diff --git a/app/src/Server.js b/app/src/Server.js index 51fc5a65..a8003118 100644 --- a/app/src/Server.js +++ b/app/src/Server.js @@ -44,7 +44,7 @@ dependencies: { * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.5.15 + * @version 1.5.16 * */ diff --git a/app/src/config.template.js b/app/src/config.template.js index 62e509b6..21b1b2bb 100644 --- a/app/src/config.template.js +++ b/app/src/config.template.js @@ -418,6 +418,11 @@ module.exports = { chatSpeechStartButton: true, chatGPT: true, }, + poll: { + pollPinButton: true, + pollMaxButton: true, + pollSaveButton: true, + }, participantsList: { saveInfoButton: true, // presenter sendFileAllButton: true, // presenter diff --git a/package.json b/package.json index c233fca6..62057aa4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalksfu", - "version": "1.5.15", + "version": "1.5.16", "description": "WebRTC SFU browser-based video calls", "main": "Server.js", "scripts": { diff --git a/public/css/Polls.css b/public/css/Polls.css index 6b2e4ee6..c3303dbd 100644 --- a/public/css/Polls.css +++ b/public/css/Polls.css @@ -3,7 +3,7 @@ position: absolute; background: var(--body-bg); padding: 20px; - border-radius: var(--border-radius); + border-radius: 10px; border: var(--border); box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); width: 100%; @@ -41,6 +41,10 @@ color: #fff !important; } +.poll-question { + font-weight: bold; +} + .form { display: flex; flex-direction: column; @@ -73,8 +77,6 @@ .poll-btns { display: flex; gap: 10px; - margin-top: 15px; - margin-bottom: 15px; } .poll-btn { @@ -174,10 +176,7 @@ .poll-header-btns { display: flex; gap: 10px; - margin-top: 15px; - margin-bottom: 15px; position: absolute; float: right; right: 20px; - font-size: 1.3rem; } diff --git a/public/js/Room.js b/public/js/Room.js index ffc2abe5..d80e2e3a 100644 --- a/public/js/Room.js +++ b/public/js/Room.js @@ -11,7 +11,7 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.5.15 + * @version 1.5.16 * */ @@ -176,19 +176,6 @@ const initMicrophoneSelect = getId('initMicrophoneSelect'); const speakerSelect = getId('speakerSelect'); const initSpeakerSelect = getId('initSpeakerSelect'); -// #################################################### -// POLLS -// #################################################### - -const createPollForm = getId('createPollForm'); -const pollsContainer = getId('pollsContainer'); -const addOptionButton = getId('addOptionButton'); -const delOptionButton = getId('delOptionButton'); -const optionsContainer = getId('optionsContainer'); -const pollSaveResultsButton = getId('pollSaveResultsButton'); -const selectedOptions = {}; -let pollOpen = false; - // #################################################### // DYNAMIC SETTINGS // #################################################### @@ -346,10 +333,11 @@ function initClient() { setTippy('chatShowParticipantsList', 'Toggle participants list', 'bottom'); setTippy('chatMaxButton', 'Maximize', 'bottom'); setTippy('chatMinButton', 'Minimize', 'bottom'); - setTippy('pollSaveResultsButton', 'Save results', 'left'); + setTippy('pollTogglePin', 'Toggle pin', 'bottom'); + setTippy('pollSaveButton', 'Save results', 'left'); setTippy('pollCloseBtn', 'Close', 'bottom'); - setTippy('addOptionButton', 'Add option', 'top'); - setTippy('delOptionButton', 'Delete option', 'top'); + setTippy('pollAddOptionBtn', 'Add option', 'top'); + setTippy('pollDelOptionBtn', 'Delete option', 'top'); setTippy('participantsSaveBtn', 'Save participants info', 'bottom'); setTippy('participantsRaiseHandBtn', 'Toggle raise hands', 'bottom'); setTippy('participantsUnreadMessagesBtn', 'Toggle unread messages', 'bottom'); @@ -1292,6 +1280,8 @@ function roomIsReady() { BUTTONS.chat.chatEmojiButton && show(chatEmojiButton); BUTTONS.chat.chatMarkdownButton && show(chatMarkdownButton); + !BUTTONS.poll.pollSaveButton && hide(pollSaveButton); + isWebkitSpeechRecognitionSupported && BUTTONS.chat.chatSpeechStartButton ? show(chatSpeechStartButton) : (BUTTONS.chat.chatSpeechStartButton = false); @@ -1315,11 +1305,14 @@ function roomIsReady() { hide(chatTogglePin); hide(chatMaxButton); hide(chatMinButton); + rc.pollMaximize(); + hide(pollTogglePin); + hide(pollMaxButton); + hide(pollMinButton); transcription.maximize(); hide(transcriptionTogglePinBtn); hide(transcriptionMaxBtn); hide(transcriptionMinBtn); - rc.pollMaximize(); } else { rc.makeDraggable(emojiPickerContainer, emojiPickerHeader); rc.makeDraggable(chatRoom, chatHeader); @@ -1338,6 +1331,8 @@ function roomIsReady() { } BUTTONS.chat.chatPinButton && show(chatTogglePin); BUTTONS.chat.chatMaxButton && show(chatMaxButton); + BUTTONS.poll.pollPinButton && show(pollTogglePin); + BUTTONS.poll.pollMaxButton && show(pollMaxButton); BUTTONS.settings.pushToTalk && show(pushToTalkDiv); BUTTONS.settings.tabRTMPStreamingBtn && show(tabRTMPStreamingBtn) && @@ -1580,20 +1575,29 @@ function handleButtons() { pollButton.onclick = () => { rc.togglePoll(); }; + pollMaxButton.onclick = () => { + rc.pollMaximize(); + }; + pollMinButton.onclick = () => { + rc.pollMinimize(); + }; pollCloseBtn.onclick = () => { rc.togglePoll(); }; - pollSaveResultsButton.onclick = () => { + pollTogglePin.onclick = () => { + rc.togglePollPin(); + }; + pollSaveButton.onclick = () => { rc.pollSaveResults(); }; - addOptionButton.onclick = () => { + pollAddOptionBtn.onclick = () => { rc.pollAddOptions(); }; - delOptionButton.onclick = () => { + pollDelOptionBtn.onclick = () => { rc.pollDeleteOptions(); }; - createPollForm.onsubmit = (e) => { - rc.pollCreateForm(e); + pollCreateForm.onsubmit = (e) => { + rc.pollCreateNewForm(e); }; transcriptionButton.onclick = () => { transcription.toggle(); diff --git a/public/js/RoomClient.js b/public/js/RoomClient.js index 737f33c9..fa699472 100644 --- a/public/js/RoomClient.js +++ b/public/js/RoomClient.js @@ -9,7 +9,7 @@ * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.5.15 + * @version 1.5.16 * */ @@ -256,6 +256,8 @@ class RoomClient { this.isZoomCenterMode = false; this.isChatOpen = false; this.isChatEmojiOpen = false; + this.isPollOpen = false; + this.isPollPinned = false; this.isSpeechSynthesisSupported = isSpeechSynthesisSupported; this.speechInMessages = false; this.showChatOnMessage = true; @@ -270,6 +272,7 @@ class RoomClient { this.camera = 'user'; this.videoQualitySelectedIndex = 0; + this.pollSelectedOptions = {}; this.chatGPTContext = []; this.chatMessages = []; this.leftMsgAvatar = null; @@ -3407,6 +3410,9 @@ class RoomClient { if (this.isChatPinned) { this.chatPin(); } + if (this.isPollPinned) { + this.pollPin(); + } if (this.transcription.isPin()) { this.transcription.pinned(); } @@ -3593,6 +3599,9 @@ class RoomClient { if (transcription.isPin()) { return userLog('info', 'Please unpin the transcription that appears to be currently pinned', 'top-end'); } + if (this.isPollPinned) { + return userLog('info', 'Please unpin the poll that appears to be currently pinned', 'top-end'); + } this.isChatPinned ? this.chatUnpin() : this.chatPin(); this.sound('click'); } @@ -4239,6 +4248,324 @@ class RoomClient { saveObjToJsonFile(this.chatMessages, 'CHAT'); } + // ############################################## + // POOLS + // ############################################## + + togglePoll() { + pollRoom.classList.toggle('show'); + if (!this.isPollOpen) { + hide(pollMinButton); + if (!this.isMobileDevice) { + BUTTONS.poll.pollMaxButton && show(pollMaxButton); + } + this.pollCenter(); + this.sound('open'); + } + this.isPollOpen = !this.isPollOpen; + if (this.isPollPinned) this.pollUnpin(); + } + + togglePollPin() { + if (transcription.isPin()) { + return userLog('info', 'Please unpin the transcription that appears to be currently pinned', 'top-end'); + } + if (this.isChatPinned) { + return userLog('info', 'Please unpin the chat that appears to be currently pinned', 'top-end'); + } + this.isPollPinned ? this.pollUnpin() : this.pollPin(); + this.sound('click'); + } + + pollPin() { + if (!this.isVideoPinned) { + this.videoMediaContainer.style.top = 0; + this.videoMediaContainer.style.width = '75%'; + this.videoMediaContainer.style.height = '100%'; + } + this.pollPinned(); + this.isPollPinned = true; + setColor(pollTogglePin, 'lime'); + resizeVideoMedia(); + chatRoom.style.resize = 'none'; + if (!this.isMobileDevice) this.makeUnDraggable(pollRoom, pollHeader); + } + + pollUnpin() { + if (!this.isVideoPinned) { + this.videoMediaContainer.style.top = 0; + this.videoMediaContainer.style.right = null; + this.videoMediaContainer.style.width = '100%'; + this.videoMediaContainer.style.height = '100%'; + } + pollRoom.style.maxWidth = '600px'; + pollRoom.style.maxHeight = '700px'; + this.pollCenter(); + this.isPollPinned = false; + setColor(pollTogglePin, 'white'); + resizeVideoMedia(); + if (!this.isMobileDevice) this.makeDraggable(pollRoom, pollHeader); + } + + pollPinned() { + pollRoom.style.position = 'absolute'; + pollRoom.style.top = 0; + pollRoom.style.right = 0; + pollRoom.style.left = null; + pollRoom.style.transform = null; + pollRoom.style.maxWidth = '25%'; + pollRoom.style.maxHeight = '100%'; + } + + pollCenter() { + pollRoom.style.position = 'fixed'; + pollRoom.style.transform = 'translate(-50%, -50%)'; + pollRoom.style.top = '50%'; + pollRoom.style.left = '50%'; + } + + pollMaximize() { + pollRoom.style.maxHeight = '100vh'; + pollRoom.style.maxWidth = '100vw'; + this.pollCenter(); + hide(pollMaxButton); + BUTTONS.poll.pollMaxButton && show(pollMinButton); + } + + pollMinimize() { + this.pollCenter(); + hide(pollMinButton); + BUTTONS.poll.pollMaxButton && show(pollMaxButton); + if (this.isPollPinned) { + this.pollPin(); + } else { + pollRoom.style.maxWidth = '600px'; + pollRoom.style.maxHeight = '700px'; + } + } + + pollsUpdate(polls) { + if (!this.isPollOpen) this.togglePoll(); + + pollsContainer.innerHTML = ''; + polls.forEach((poll, index) => { + const pollDiv = document.createElement('div'); + pollDiv.className = 'poll'; + + const question = document.createElement('p'); + question.className = 'poll-question'; + question.textContent = poll.question; + pollDiv.appendChild(question); + + const options = document.createElement('div'); + options.className = 'options'; + + poll.options.forEach((option) => { + const optionDiv = document.createElement('div'); + const input = document.createElement('input'); + input.type = 'radio'; + input.name = `poll${index}`; + input.value = option; + if (this.pollSelectedOptions[index] === option) { + input.checked = true; + } + + input.addEventListener('change', () => { + this.pollSelectedOptions[index] = option; + this.socket.emit('vote', { pollIndex: index, option }); + }); + + const label = document.createElement('label'); + label.textContent = option; + + optionDiv.appendChild(input); + optionDiv.appendChild(label); + options.appendChild(optionDiv); + }); + pollDiv.appendChild(options); + + // Only the presenters + // if (isPresenter) { + const pollButtonsDiv = document.createElement('div'); + pollButtonsDiv.className = 'poll-btns'; + + // Toggle voters button + const toggleButton = document.createElement('button'); + const toggleButtonIcon = document.createElement('i'); + toggleButtonIcon.className = 'fas fa-users'; + toggleButton.id = 'toggleVoters'; + toggleButton.className = 'view-btn'; + // Append the icon to the button + toggleButton.insertBefore(toggleButtonIcon, toggleButton.firstChild); + toggleButton.addEventListener('click', () => { + votersList.style.display === 'none' + ? (votersList.style.display = 'block') + : (votersList.style.display = 'none'); + }); + pollButtonsDiv.appendChild(toggleButton); + + // Edit poll button using swal + const editPollButton = document.createElement('button'); + const editPollButtonIcon = document.createElement('i'); + editPollButtonIcon.className = 'fas fa-pen-to-square'; + editPollButton.id = 'editPoll'; + editPollButton.className = 'poll-btn'; + editPollButton.insertBefore(editPollButtonIcon, editPollButton.firstChild); + editPollButton.addEventListener('click', () => { + Swal.fire({ + allowOutsideClick: false, + allowEscapeKey: false, + background: swalBackground, + title: 'Edit Poll', + html: this.createPollInputs(poll), + focusConfirm: false, + showCancelButton: true, + confirmButtonText: 'Save', + cancelButtonText: 'Cancel', + cancelButtonColor: '#dc3545', + preConfirm: () => { + const newQuestion = document.getElementById('swal-input-question').value; + const newOptions = this.getPollOptions(poll.options.length); + this.socket.emit('editPoll', { + index, + question: newQuestion, + options: newOptions, + peer_name: this.peer_name, + peer_uuid: this.peer_uuid, + }); + }, + showClass: { popup: 'animate__animated animate__fadeInDown' }, + hideClass: { popup: 'animate__animated animate__fadeOutUp' }, + }); + }); + pollButtonsDiv.appendChild(editPollButton); + + // Delete poll button + const deletePollButton = document.createElement('button'); + const deletePollButtonIcon = document.createElement('i'); + deletePollButtonIcon.className = 'fas fa-minus'; + deletePollButton.id = 'delPoll'; + deletePollButton.className = 'del-btn'; + deletePollButton.insertBefore(deletePollButtonIcon, deletePollButton.firstChild); + deletePollButton.addEventListener('click', () => { + this.socket.emit('deletePoll', { index, peer_name: this.peer_name, peer_uuid: this.peer_uuid }); + }); + pollButtonsDiv.appendChild(deletePollButton); + + // Append buttons to poll + pollDiv.appendChild(pollButtonsDiv); + + // Create voter lists + const votersList = document.createElement('ul'); + votersList.style.display = 'none'; + for (const [user, vote] of Object.entries(poll.voters)) { + const voter = document.createElement('li'); + voter.textContent = `${user}: ${vote}`; + votersList.appendChild(voter); + } + pollDiv.appendChild(votersList); + // } + + pollsContainer.appendChild(pollDiv); + + if (!this.isMobileDevice) { + setTippy('toggleVoters', 'Toggle voters', 'top'); + setTippy('delPoll', 'Delete poll', 'top'); + setTippy('editPoll', 'Edit poll', 'top'); + } + }); + } + + pollCreateNewForm(e) { + e.preventDefault(); + + const question = e.target.question.value; + const optionInputs = document.querySelectorAll('.option-input'); + const options = Array.from(optionInputs).map((input) => input.value.trim()); + + this.socket.emit('createPoll', { question, options }); + + e.target.reset(); + optionsContainer.innerHTML = ''; + const initialOptionInput = document.createElement('input'); + initialOptionInput.type = 'text'; + initialOptionInput.name = 'option'; + initialOptionInput.className = 'option-input'; + initialOptionInput.required = true; + optionsContainer.appendChild(initialOptionInput); + } + + pollAddOptions() { + const optionInput = document.createElement('input'); + optionInput.type = 'text'; + optionInput.name = 'option'; + optionInput.className = 'option-input'; + optionInput.required = true; + optionsContainer.appendChild(optionInput); + } + + pollDeleteOptions() { + const optionInputs = document.querySelectorAll('.option-input'); + if (optionInputs.length > 1) { + optionsContainer.removeChild(optionInputs[optionInputs.length - 1]); + } + } + + createPollInputs(poll) { + const questionInput = ``; + const optionsInputs = poll.options + .map((option, i) => ``) + .join(''); + return questionInput + optionsInputs; + } + + getPollOptions(optionCount) { + const options = []; + for (let i = 0; i < optionCount; i++) { + options.push(document.getElementById(`swal-input-option${i}`).value); + } + return options; + } + + pollSaveResults() { + const polls = document.querySelectorAll('.poll'); + const results = []; + + polls.forEach((poll, index) => { + const question = poll.querySelector('.poll-question').textContent; + const options = poll.querySelectorAll('.options div label'); + + const optionsText = Array.from(options).reduce((acc, option, index) => { + acc[index + 1] = option.textContent.trim(); + return acc; + }, {}); + + const votersList = poll.querySelector('ul'); + const voters = Array.from(votersList.querySelectorAll('li')).reduce((acc, li) => { + const [name, vote] = li.textContent.split(':').map((item) => item.trim()); + acc[name] = vote; + return acc; + }, {}); + + results.push({ + Poll: `${index + 1}`, + question: question, + options: optionsText, + voters: voters, + }); + }); + + results.length > 0 + ? saveObjToJsonFile(results, 'Poll') + : this.userLog('info', 'No polling data available to save', 'top-end'); + } + + getPollFileName() { + const dateTime = getDataTimeStringFormat(); + const roomName = this.room_id.trim(); + return `Poll_${roomName}_${dateTime}.txt`; + } + // #################################################### // RECORDING // #################################################### @@ -7739,256 +8066,6 @@ class RoomClient { }); } - // ############################################## - // POOLS - // ############################################## - - togglePoll() { - const pollRoom = this.getId('pollRoom'); - pollRoom.classList.toggle('show'); - if (!pollOpen) { - this.pollCenter(); - this.sound('open'); - } - pollOpen = !pollOpen; - } - - pollCenter() { - const pollRoom = this.getId('pollRoom'); - pollRoom.style.position = 'fixed'; - pollRoom.style.transform = 'translate(-50%, -50%)'; - pollRoom.style.top = '50%'; - pollRoom.style.left = '50%'; - } - - pollMaximize() { - const pollRoom = this.getId('pollRoom'); - pollRoom.style.maxHeight = '100vh'; - pollRoom.style.maxWidth = '100vw'; - } - - pollsUpdate(polls) { - if (!pollOpen) this.togglePoll(); - - pollsContainer.innerHTML = ''; - polls.forEach((poll, index) => { - const pollDiv = document.createElement('div'); - pollDiv.className = 'poll'; - - const question = document.createElement('h3'); - question.className = 'poll-h3'; - question.textContent = poll.question; - pollDiv.appendChild(question); - - const options = document.createElement('div'); - options.className = 'options'; - - poll.options.forEach((option) => { - const optionDiv = document.createElement('div'); - const input = document.createElement('input'); - input.type = 'radio'; - input.name = `poll${index}`; - input.value = option; - if (selectedOptions[index] === option) { - input.checked = true; - } - - input.addEventListener('change', () => { - selectedOptions[index] = option; - this.socket.emit('vote', { pollIndex: index, option }); - }); - - const label = document.createElement('label'); - label.textContent = option; - - optionDiv.appendChild(input); - optionDiv.appendChild(label); - options.appendChild(optionDiv); - }); - pollDiv.appendChild(options); - - // Only the presenters - // if (isPresenter) { - const pollButtonsDiv = document.createElement('div'); - pollButtonsDiv.className = 'poll-btns'; - - // Toggle voters button - const toggleButton = document.createElement('button'); - const toggleButtonIcon = document.createElement('i'); - toggleButtonIcon.className = 'fas fa-users'; - toggleButton.id = 'toggleVoters'; - toggleButton.className = 'view-btn'; - // Append the icon to the button - toggleButton.insertBefore(toggleButtonIcon, toggleButton.firstChild); - toggleButton.addEventListener('click', () => { - votersList.style.display === 'none' - ? (votersList.style.display = 'block') - : (votersList.style.display = 'none'); - }); - pollButtonsDiv.appendChild(toggleButton); - - // Edit poll button using swal - const editPollButton = document.createElement('button'); - const editPollButtonIcon = document.createElement('i'); - editPollButtonIcon.className = 'fas fa-pen-to-square'; - editPollButton.id = 'editPoll'; - editPollButton.className = 'poll-btn'; - editPollButton.insertBefore(editPollButtonIcon, editPollButton.firstChild); - editPollButton.addEventListener('click', () => { - Swal.fire({ - allowOutsideClick: false, - allowEscapeKey: false, - background: swalBackground, - title: 'Edit Poll', - html: this.createPollInputs(poll), - focusConfirm: false, - showCancelButton: true, - confirmButtonText: 'Save', - cancelButtonText: 'Cancel', - cancelButtonColor: '#dc3545', - preConfirm: () => { - const newQuestion = document.getElementById('swal-input-question').value; - const newOptions = this.getPollOptions(poll.options.length); - this.socket.emit('editPoll', { - index, - question: newQuestion, - options: newOptions, - peer_name: this.peer_name, - peer_uuid: this.peer_uuid, - }); - }, - showClass: { popup: 'animate__animated animate__fadeInDown' }, - hideClass: { popup: 'animate__animated animate__fadeOutUp' }, - }); - }); - pollButtonsDiv.appendChild(editPollButton); - - // Delete poll button - const deletePollButton = document.createElement('button'); - const deletePollButtonIcon = document.createElement('i'); - deletePollButtonIcon.className = 'fas fa-minus'; - deletePollButton.id = 'delPoll'; - deletePollButton.className = 'del-btn'; - deletePollButton.insertBefore(deletePollButtonIcon, deletePollButton.firstChild); - deletePollButton.addEventListener('click', () => { - this.socket.emit('deletePoll', { index, peer_name: this.peer_name, peer_uuid: this.peer_uuid }); - }); - pollButtonsDiv.appendChild(deletePollButton); - - // Append buttons to poll - pollDiv.appendChild(pollButtonsDiv); - - // Create voter lists - const votersList = document.createElement('ul'); - votersList.style.display = 'none'; - for (const [user, vote] of Object.entries(poll.voters)) { - const voter = document.createElement('li'); - voter.textContent = `${user}: ${vote}`; - votersList.appendChild(voter); - } - pollDiv.appendChild(votersList); - // } - - pollsContainer.appendChild(pollDiv); - - if (!this.isMobileDevice) { - setTippy('toggleVoters', 'Toggle voters', 'top'); - setTippy('delPoll', 'Delete poll', 'top'); - setTippy('editPoll', 'Edit poll', 'top'); - } - }); - } - - pollCreateForm(e) { - e.preventDefault(); - - const question = e.target.question.value; - const optionInputs = document.querySelectorAll('.option-input'); - const options = Array.from(optionInputs).map((input) => input.value.trim()); - - this.socket.emit('createPoll', { question, options }); - - e.target.reset(); - optionsContainer.innerHTML = ''; - const initialOptionInput = document.createElement('input'); - initialOptionInput.type = 'text'; - initialOptionInput.name = 'option'; - initialOptionInput.className = 'option-input'; - initialOptionInput.required = true; - optionsContainer.appendChild(initialOptionInput); - } - - pollAddOptions() { - const optionInput = document.createElement('input'); - optionInput.type = 'text'; - optionInput.name = 'option'; - optionInput.className = 'option-input'; - optionInput.required = true; - optionsContainer.appendChild(optionInput); - } - - pollDeleteOptions() { - const optionInputs = document.querySelectorAll('.option-input'); - if (optionInputs.length > 1) { - optionsContainer.removeChild(optionInputs[optionInputs.length - 1]); - } - } - - createPollInputs(poll) { - const questionInput = ``; - const optionsInputs = poll.options - .map((option, i) => ``) - .join(''); - return questionInput + optionsInputs; - } - - getPollOptions(optionCount) { - const options = []; - for (let i = 0; i < optionCount; i++) { - options.push(document.getElementById(`swal-input-option${i}`).value); - } - return options; - } - - pollSaveResults() { - const polls = document.querySelectorAll('.poll'); - const results = []; - - polls.forEach((poll, index) => { - const question = poll.querySelector('.poll-h3').textContent; - const options = poll.querySelectorAll('.options div label'); - - const optionsText = Array.from(options).reduce((acc, option, index) => { - acc[index + 1] = option.textContent.trim(); - return acc; - }, {}); - - const votersList = poll.querySelector('ul'); - const voters = Array.from(votersList.querySelectorAll('li')).reduce((acc, li) => { - const [name, vote] = li.textContent.split(':').map((item) => item.trim()); - acc[name] = vote; - return acc; - }, {}); - - results.push({ - Poll: `${index + 1}`, - question: question, - options: optionsText, - voters: voters, - }); - }); - - results.length > 0 - ? saveObjToJsonFile(results, 'Poll') - : this.userLog('info', 'No polling data available to save', 'top-end'); - } - - getPollFileName() { - const dateTime = getDataTimeStringFormat(); - const roomName = this.room_id.trim(); - return `Poll_${roomName}_${dateTime}.txt`; - } - sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } diff --git a/public/js/Rules.js b/public/js/Rules.js index 3c217b4d..5d5ce4e1 100644 --- a/public/js/Rules.js +++ b/public/js/Rules.js @@ -85,6 +85,11 @@ let BUTTONS = { chatSpeechStartButton: true, chatGPT: true, }, + poll: { + pollPinButton: true, + pollMaxButton: true, + pollSaveButton: true, + }, participantsList: { saveInfoButton: true, // presenter sendFileAllButton: true, // presenter @@ -221,6 +226,7 @@ function handleRulesBroadcasting() { //BUTTONS.main.raiseHandButton = false; BUTTONS.main.whiteboardButton = false; //BUTTONS.main.emojiRoomButton = false, + //BUTTONS.main.pollButton = false; BUTTONS.main.transcriptionButton = false; BUTTONS.main.settingsButton = false; BUTTONS.participantsList.saveInfoButton = false; @@ -254,6 +260,7 @@ function handleRulesBroadcasting() { //elemDisplay('raiseHandButton', false); elemDisplay('whiteboardButton', false); //elemDisplay('emojiRoomButton', false); + //elemDisplay('pollButton', false); elemDisplay('transcriptionButton', false); elemDisplay('lockRoomButton', false); elemDisplay('unlockRoomButton', false); diff --git a/public/views/Room.html b/public/views/Room.html index 1136a429..35b463e1 100644 --- a/public/views/Room.html +++ b/public/views/Room.html @@ -1440,15 +1440,26 @@