[mirotalksfu] - #22 add real-time polls
هذا الالتزام موجود في:
@@ -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.4.99
|
||||
* @version 1.5.10
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -176,6 +176,18 @@ 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 selectedOptions = {};
|
||||
let pollOpen = false;
|
||||
|
||||
// ####################################################
|
||||
// DYNAMIC SETTINGS
|
||||
// ####################################################
|
||||
@@ -333,6 +345,7 @@ function initClient() {
|
||||
setTippy('chatShowParticipantsList', 'Toggle participants list', 'bottom');
|
||||
setTippy('chatMaxButton', 'Maximize', 'bottom');
|
||||
setTippy('chatMinButton', 'Minimize', 'bottom');
|
||||
setTippy('pollCloseBtn', 'Close', 'bottom');
|
||||
setTippy('participantsSaveBtn', 'Save participants info', 'bottom');
|
||||
setTippy('participantsRaiseHandBtn', 'Toggle raise hands', 'bottom');
|
||||
setTippy('participantsUnreadMessagesBtn', 'Toggle unread messages', 'bottom');
|
||||
@@ -372,6 +385,7 @@ function refreshMainButtonsToolTipPlacement() {
|
||||
setTippy('emojiRoomButton', 'Toggle emoji reaction', placement);
|
||||
setTippy('swapCameraButton', 'Swap the camera', placement);
|
||||
setTippy('chatButton', 'Toggle the chat', placement);
|
||||
setTippy('pollButton', 'Toggle the poll', placement);
|
||||
setTippy('transcriptionButton', 'Toggle transcription', placement);
|
||||
setTippy('whiteboardButton', 'Toggle the whiteboard', placement);
|
||||
setTippy('settingsButton', 'Toggle the settings', placement);
|
||||
@@ -1267,6 +1281,7 @@ function roomIsReady() {
|
||||
hide(tabRecordingBtn);
|
||||
}
|
||||
BUTTONS.main.chatButton && show(chatButton);
|
||||
BUTTONS.main.pollButton && show(pollButton);
|
||||
BUTTONS.main.raiseHandButton && show(raiseHandButton);
|
||||
BUTTONS.main.emojiRoomButton && show(emojiRoomButton);
|
||||
!BUTTONS.chat.chatSaveButton && hide(chatSaveButton);
|
||||
@@ -1300,9 +1315,11 @@ function roomIsReady() {
|
||||
hide(transcriptionTogglePinBtn);
|
||||
hide(transcriptionMaxBtn);
|
||||
hide(transcriptionMinBtn);
|
||||
rc.pollMaximize();
|
||||
} else {
|
||||
rc.makeDraggable(emojiPickerContainer, emojiPickerHeader);
|
||||
rc.makeDraggable(chatRoom, chatHeader);
|
||||
rc.makeDraggable(pollRoom, pollHeader);
|
||||
rc.makeDraggable(mySettings, mySettingsHeader);
|
||||
rc.makeDraggable(whiteboard, whiteboardHeader);
|
||||
rc.makeDraggable(sendFileDiv, imgShareSend);
|
||||
@@ -1555,6 +1572,22 @@ function handleButtons() {
|
||||
rc.toggleShowParticipants();
|
||||
}
|
||||
};
|
||||
// Polls
|
||||
pollButton.onclick = () => {
|
||||
rc.togglePoll();
|
||||
};
|
||||
pollCloseBtn.onclick = () => {
|
||||
rc.togglePoll();
|
||||
};
|
||||
addOptionButton.onclick = () => {
|
||||
rc.pollAddOptions();
|
||||
};
|
||||
delOptionButton.onclick = () => {
|
||||
rc.pollDeleteOptions();
|
||||
};
|
||||
createPollForm.onsubmit = (e) => {
|
||||
rc.pollCreateForm(e);
|
||||
};
|
||||
transcriptionButton.onclick = () => {
|
||||
transcription.toggle();
|
||||
};
|
||||
@@ -4240,7 +4273,7 @@ function showAbout() {
|
||||
imageUrl: image.about,
|
||||
customClass: { image: 'img-about' },
|
||||
position: 'center',
|
||||
title: 'WebRTC SFU v1.4.99',
|
||||
title: 'WebRTC SFU v1.5.10',
|
||||
html: `
|
||||
<br />
|
||||
<div id="about">
|
||||
|
||||
@@ -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.4.99
|
||||
* @version 1.5.10
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -551,6 +551,10 @@ class RoomClient {
|
||||
elemDisplay('tabRTMPStreamingBtn', false);
|
||||
}
|
||||
}
|
||||
// There is polls
|
||||
if (room.thereIsPolls) {
|
||||
this.socket.emit('updatePoll');
|
||||
}
|
||||
}
|
||||
|
||||
// PARTICIPANTS
|
||||
@@ -869,6 +873,7 @@ class RoomClient {
|
||||
this.socket.on('errorRTMP', this.handleErrorRTMP);
|
||||
this.socket.on('endRTMPfromURL', this.handleEndRTMPfromURL);
|
||||
this.socket.on('errorRTMPfromURL', this.handleErrorRTMPfromURL);
|
||||
this.socket.on('updatePolls', this.handleUpdatePolls);
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
@@ -1039,6 +1044,10 @@ class RoomClient {
|
||||
this.errorRTMPfromURL(data);
|
||||
};
|
||||
|
||||
handleUpdatePolls = (data) => {
|
||||
this.pollsUpdate(data);
|
||||
};
|
||||
|
||||
// ####################################################
|
||||
// SERVER AWAY/MAINTENANCE
|
||||
// ####################################################
|
||||
@@ -7730,6 +7739,216 @@ 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.textContent = ' Toggle Voters';
|
||||
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.textContent = ' Edit Poll';
|
||||
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.textContent = ' Delete Poll';
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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 = `<input id="swal-input-question" class="swal2-input" value="${poll.question}">`;
|
||||
const optionsInputs = poll.options
|
||||
.map(
|
||||
(option, i) => `
|
||||
<input id="swal-input-option${i}" class="swal2-input" value="${option}">
|
||||
`,
|
||||
)
|
||||
.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;
|
||||
}
|
||||
|
||||
sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ let BUTTONS = {
|
||||
startScreenButton: true,
|
||||
swapCameraButton: true,
|
||||
chatButton: true,
|
||||
pollButton: true,
|
||||
raiseHandButton: true,
|
||||
transcriptionButton: true,
|
||||
whiteboardButton: true,
|
||||
|
||||
@@ -28,6 +28,8 @@ const commands = {
|
||||
chatOn: 'open the chat',
|
||||
chatSend: 'send',
|
||||
chatOff: 'close the chat',
|
||||
pollOn: 'open the poll',
|
||||
pollOff: 'close the poll',
|
||||
toggleTr: 'toggle transcription',
|
||||
whiteboardOn: 'open the whiteboard',
|
||||
whiteboardOff: 'close the whiteboard',
|
||||
@@ -186,6 +188,14 @@ function execVoiceCommands(transcript) {
|
||||
printCommand(commands.chatOn);
|
||||
chatButton.click();
|
||||
break;
|
||||
case commands.pollOn:
|
||||
printCommand(commands.pollOn);
|
||||
pollButton.click();
|
||||
break;
|
||||
case commands.pollOff:
|
||||
printCommand(commands.pollOff);
|
||||
pollCloseBtn.click();
|
||||
break;
|
||||
case commands.chatSend:
|
||||
printCommand(commands.chatSend);
|
||||
chatSendButton.click();
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم