[mirotalk] - improve widget

This commit is contained in:
Miroslav Pejic
2025-08-10 22:28:53 +02:00
parent 5308f2d81c
commit 4ea46bbfad
9 changed files with 248 additions and 62 deletions
+7
View File
@@ -17,6 +17,13 @@ module.exports = {
expertImages: [
'https://photo.cloudron.pocketsolution.net/uploads/original/95/7d/a5f7f7a2c89a5fee7affda5f013c.jpeg',
],
buttons: {
audio: true,
video: true,
screen: true,
chat: true,
join: true,
},
checkOnlineStatus: false,
isOnline: true,
customMessages: {
+1 -1
View File
@@ -45,7 +45,7 @@ dependencies: {
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.5.55
* @version 1.5.56
*
*/
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mirotalk",
"version": "1.5.55",
"version": "1.5.56",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mirotalk",
"version": "1.5.55",
"version": "1.5.56",
"license": "AGPL-3.0",
"dependencies": {
"@mattermost/client": "10.9.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mirotalk",
"version": "1.5.55",
"version": "1.5.56",
"description": "A free WebRTC browser-based video call",
"main": "server.js",
"scripts": {
+8 -1
View File
@@ -48,6 +48,13 @@ let brand = {
expertImages: [
'https://photo.cloudron.pocketsolution.net/uploads/original/95/7d/a5f7f7a2c89a5fee7affda5f013c.jpeg',
],
buttons: {
audio: true,
video: true,
screen: true,
chat: true,
join: true,
},
checkOnlineStatus: false,
isOnline: true,
customMessages: {
@@ -96,7 +103,7 @@ let brand = {
},
about: {
imageUrl: '../images/mirotalk-logo.gif',
title: 'WebRTC P2P v1.5.55',
title: 'WebRTC P2P v1.5.56',
html: `
<button
id="support-button"
+2 -2
View File
@@ -15,7 +15,7 @@
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.5.55
* @version 1.5.56
*
*/
@@ -11273,7 +11273,7 @@ function showAbout() {
Swal.fire({
background: swBg,
position: 'center',
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.5.55',
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.5.56',
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
customClass: { image: 'img-about' },
html: `
+42 -13
View File
@@ -16,6 +16,13 @@ class MiroTalkWidget {
'https://i.pravatar.cc/40?img=2',
'https://i.pravatar.cc/40?img=3',
],
buttons: {
audio: true,
video: true,
screen: true,
chat: true,
join: true,
},
checkOnlineStatus: false,
isOnline: true,
customMessages: {
@@ -223,21 +230,28 @@ class MiroTalkWidget {
}
createActionButtons() {
const buttons = [
{ action: 'startAudioCall', icon: this.getAudioIcon(), text: 'Start Audio Call' },
{ action: 'startVideoCall', icon: this.getVideoIcon(), text: 'Start Video Call' },
];
const flags = this.options.supportWidget.buttons || {};
const buttons = [];
// Only show "Start Screen Share" if displayMedia is supported
if (navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === 'function') {
if (flags.audio) {
buttons.push({ action: 'startAudioCall', icon: this.getAudioIcon(), text: 'Start Audio Call' });
}
if (flags.video) {
buttons.push({ action: 'startVideoCall', icon: this.getVideoIcon(), text: 'Start Video Call' });
}
if (flags.screen && navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === 'function') {
buttons.push({ action: 'startScreenShare', icon: this.getScreenIcon(), text: 'Start Screen Share' });
}
if (flags.chat) {
buttons.push({ action: 'startChat', icon: this.getChatIcon(), text: 'Start Chat' });
}
if (flags.join) {
buttons.push({ action: 'joinRoom', icon: this.getJoinIcon(), text: 'Join Room' });
}
// Add chat button
buttons.push({ action: 'startChat', icon: this.getChatIcon(), text: 'Start Chat' });
// Insert "Start Screen Share" before "Join Room" if present
buttons.push({ action: 'joinRoom', icon: this.getJoinIcon(), text: 'Join Room' });
if (!buttons.length) {
return `<div class="no-actions">No actions available</div>`;
}
return buttons
.map(
@@ -245,8 +259,7 @@ class MiroTalkWidget {
<button class="btn" onclick="miroTalkWidgetAction('${btn.action}', this)">
<div class="btn-icon">${btn.icon}</div>
<span class="btn-text">${btn.text}</span>
</button>
`
</button>`
)
.join('');
}
@@ -709,6 +722,21 @@ document.addEventListener('DOMContentLoaded', function () {
if (!autoInit) return;
try {
const buttonsAttr = autoInit.getAttribute('data-buttons');
let buttonsConfig = { ...MiroTalkWidget.DEFAULT_OPTIONS.supportWidget.buttons };
if (buttonsAttr) {
// Normalize and map
const requested = buttonsAttr
.split(',')
.map((b) => b.trim().toLowerCase())
.filter(Boolean);
// Start all false then enable requested valid keys
buttonsConfig = { audio: false, video: false, screen: false, chat: false, join: false };
requested.forEach((key) => {
if (key in buttonsConfig) buttonsConfig[key] = true;
});
}
const config = {
domain: autoInit.getAttribute('data-domain') || window.location.host,
roomId: autoInit.getAttribute('data-room') || 'support-room',
@@ -756,6 +784,7 @@ document.addEventListener('DOMContentLoaded', function () {
expertImages: config.expertImages,
checkOnlineStatus: config.checkOnline,
customMessages: config.customMessages,
buttons: buttonsConfig,
},
});
}
+1
View File
@@ -15,6 +15,7 @@
data-position="bottom-right"
data-check-online="false"
data-expert-images="https://i.pravatar.cc/40?img=1,https://i.pravatar.cc/40?img=2,https://i.pravatar.cc/40?img=3"
data-buttons="audio,video,screen,chat,join"
data-heading="Need Help?"
data-subheading="Get instant support from our expert team!"
data-connect-text="connect in &lt; 5 seconds"
+184 -42
View File
@@ -346,6 +346,63 @@
display: none;
}
.button-toggle-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 10px 14px;
margin-top: 8px;
}
.toggle {
position: relative;
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
font-weight: 600;
background: #eef1f4;
border: 1px solid #d5dade;
padding: 8px 10px 8px 40px;
border-radius: 8px;
cursor: pointer;
user-select: none;
transition:
background 0.25s,
border-color 0.25s;
}
.toggle input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.toggle::before {
content: '';
position: absolute;
left: 10px;
width: 20px;
height: 20px;
border-radius: 6px;
background: linear-gradient(135deg, #9aa0a6, #7e858c);
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.5);
transition: background 0.25s;
}
.toggle.checked {
background: #e5faf0;
border-color: #00c853;
color: #116d3d;
}
.toggle.checked::before {
background: linear-gradient(135deg, #00e676, #00c853);
}
.toggle:active {
transform: scale(0.97);
}
@media (max-width: 768px) {
.content {
grid-template-columns: 1fr;
@@ -453,6 +510,30 @@
<label for="check-online">Check Online Status <span class="feature-badge">PRO</span></label>
</div>
</div>
<!-- Action Buttons -->
<div class="form-group">
<label class="tooltip" data-tooltip="Select which action buttons to include">
Action Buttons
</label>
<div id="action-buttons" class="button-toggle-grid">
<label class="toggle" data-btn="audio"
><input type="checkbox" id="btn-audio" checked />Audio</label
>
<label class="toggle" data-btn="video"
><input type="checkbox" id="btn-video" checked />Video</label
>
<label class="toggle" data-btn="screen"
><input type="checkbox" id="btn-screen" checked />Screen</label
>
<label class="toggle" data-btn="chat"
><input type="checkbox" id="btn-chat" checked />Chat</label
>
<label class="toggle" data-btn="join"
><input type="checkbox" id="btn-join" checked />Join</label
>
</div>
</div>
</div>
<!-- Customization Form -->
@@ -555,6 +636,11 @@ https://i.pravatar.cc/40?img=1,https://i.pravatar.cc/40?img=2,https://i.pravatar
offlineText: document.getElementById('offline-text'),
poweredBy: document.getElementById('powered-by'),
expertImages: document.getElementById('expert-images'),
btnAudio: document.getElementById('btn-audio'),
btnVideo: document.getElementById('btn-video'),
btnScreen: document.getElementById('btn-screen'),
btnChat: document.getElementById('btn-chat'),
btnJoin: document.getElementById('btn-join'),
};
const generatedCode = document.getElementById('generated-code');
@@ -579,36 +665,45 @@ https://i.pravatar.cc/40?img=1,https://i.pravatar.cc/40?img=2,https://i.pravatar
'https://i.pravatar.cc/40?img=1,https://i.pravatar.cc/40?img=2,https://i.pravatar.cc/40?img=3',
};
const buttonsSelected = [];
if (form.btnAudio.checked) buttonsSelected.push('audio');
if (form.btnVideo.checked) buttonsSelected.push('video');
if (form.btnScreen.checked) buttonsSelected.push('screen');
if (form.btnChat.checked) buttonsSelected.push('chat');
if (form.btnJoin.checked) buttonsSelected.push('join');
const buttonsAttr = buttonsSelected.join(',');
const html = `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MiroTalk Widget</title>
<script src="https://${config.domain}/js/Widget.js"><\/script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>MiroTalk Widget</title>
<script src="https://${config.domain}/js/Widget.js"><\/script>
</head>
<body>
<div
id="mirotalk-widget"
data-mirotalk-auto
data-domain="${config.domain}"
data-room="${config.room}"
data-theme="${config.theme}"
data-widget-type="support"
data-position="${config.position}"
data-widget-state="${config.widgetState}"
data-check-online="${config.checkOnline}"
data-expert-images="${escapeHtml(config.expertImages)}"
data-heading="${escapeHtml(config.heading)}"
data-subheading="${escapeHtml(config.subheading)}"
data-connect-text="${escapeHtml(config.connectText)}"
data-online-text="${escapeHtml(config.onlineText)}"
data-offline-text="${escapeHtml(config.offlineText)}"
data-powered-by="${escapeHtml(config.poweredBy)}"
></div>
<div
id="mirotalk-widget"
data-mirotalk-auto
data-domain="${config.domain}"
data-room="${config.room}"
data-theme="${config.theme}"
data-widget-type="support"
data-position="${config.position}"
data-widget-state="${config.widgetState}"
data-buttons="${buttonsAttr}"
data-check-online="${config.checkOnline}"
data-expert-images="${escapeHtml(config.expertImages)}"
data-heading="${escapeHtml(config.heading)}"
data-subheading="${escapeHtml(config.subheading)}"
data-connect-text="${escapeHtml(config.connectText)}"
data-online-text="${escapeHtml(config.onlineText)}"
data-offline-text="${escapeHtml(config.offlineText)}"
data-powered-by="${escapeHtml(config.poweredBy)}"
></div>
</body>
</html>`;
generatedCode.value = html;
}
@@ -668,19 +763,25 @@ https://i.pravatar.cc/40?img=1,https://i.pravatar.cc/40?img=2,https://i.pravatar
form.poweredBy.value = 'Powered by MiroTalk';
form.expertImages.value =
'https://i.pravatar.cc/40?img=1,https://i.pravatar.cc/40?img=2,https://i.pravatar.cc/40?img=3';
form.btnAudio.checked =
form.btnVideo.checked =
form.btnScreen.checked =
form.btnChat.checked =
form.btnJoin.checked =
true;
toggleLabels.forEach(syncToggleVisual);
generateCode();
saveFormData();
}
// Auto-save form data to localStorage
function saveFormData() {
const data = {};
Object.keys(form).forEach((key) => {
if (form[key].type === 'checkbox') {
data[key] = form[key].checked;
} else {
data[key] = form[key].value;
}
if (!form[key]) return;
if (form[key].type === 'checkbox') data[key] = form[key].checked;
else data[key] = form[key].value;
});
localStorage.setItem('mirotalk-widget-maker', JSON.stringify(data));
}
@@ -691,27 +792,68 @@ https://i.pravatar.cc/40?img=1,https://i.pravatar.cc/40?img=2,https://i.pravatar
const data = JSON.parse(localStorage.getItem('mirotalk-widget-maker'));
if (data) {
Object.keys(data).forEach((key) => {
if (form[key]) {
if (form[key].type === 'checkbox') {
form[key].checked = data[key];
} else {
form[key].value = data[key];
}
}
if (!form[key]) return;
if (form[key].type === 'checkbox') form[key].checked = data[key];
else form[key].value = data[key];
});
generateCode();
} else {
// No saved data -> default all action buttons checked
setAllActionButtons(true);
}
} catch (err) {
// If somehow all buttons ended up unchecked, enforce default all on first load
if (
![form.btnAudio, form.btnVideo, form.btnScreen, form.btnChat, form.btnJoin].some(
(b) => b.checked
)
) {
setAllActionButtons(true);
}
toggleLabels.forEach(syncToggleVisual);
generateCode();
} catch (e) {
console.warn('Failed to load saved form data');
setAllActionButtons(true);
toggleLabels.forEach(syncToggleVisual);
generateCode();
}
}
function setAllActionButtons(state) {
form.btnAudio.checked =
form.btnVideo.checked =
form.btnScreen.checked =
form.btnChat.checked =
form.btnJoin.checked =
state;
}
// toggle handling (must be before loadFormData())
const toggleLabels = Array.from(document.querySelectorAll('#action-buttons .toggle'));
function syncToggleVisual(label) {
const cb = label.querySelector('input[type=checkbox]');
label.classList.toggle('checked', cb.checked);
}
// Attach handlers
toggleLabels.forEach((label) => {
const cb = label.querySelector('input[type=checkbox]');
const onChange = () => {
syncToggleVisual(label);
generateCode();
saveFormData();
};
cb.addEventListener('change', onChange);
// Initial state
syncToggleVisual(label);
});
// Add event listeners to all form elements
Object.values(form).forEach((element) => {
element.addEventListener('input', generateCode);
element.addEventListener('change', generateCode);
element.addEventListener('input', saveFormData);
element.addEventListener('change', saveFormData);
Object.values(form).forEach((el) => {
el.addEventListener('input', generateCode);
el.addEventListener('change', generateCode);
el.addEventListener('input', saveFormData);
el.addEventListener('change', saveFormData);
});
// Add event listeners for buttons