Skip to content

Commit b13066d

Browse files
[mirotalk] - feat: dynamic theme cards for server-defined custom themes
1 parent 8062398 commit b13066d

7 files changed

Lines changed: 177 additions & 36 deletions

File tree

.env.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# ====================================================
2-
# MiroTalk P2P v.1.7.92 - Environment Configuration
2+
# MiroTalk P2P v.1.7.93 - Environment Configuration
33
# ====================================================
44

55
# App environment

app/src/config.template.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/**
44
* ==============================================
5-
* MiroTalk P2P v.1.7.92 - Configuration File
5+
* MiroTalk P2P v.1.7.93 - Configuration File
66
* ==============================================
77
*
88
* This file is the central configuration source.
@@ -379,6 +379,43 @@ module.exports = {
379379
},
380380
//...
381381
},
382+
// ==========================================
383+
// Themes
384+
// ==========================================
385+
/**
386+
* Theme definitions — CSS custom properties for each theme.
387+
* Admins can override individual themes or add new ones.
388+
* The client merges these with built-in defaults, so you
389+
* only need to specify the properties you want to change.
390+
*/
391+
themes: {
392+
/* Example: override dark theme background
393+
dark: {
394+
'--body-bg': 'radial-gradient(#1a1a2e, #0a0a14)',
395+
'--msger-bg': 'radial-gradient(#1a1a2e, #0a0a14)',
396+
},
397+
*/
398+
/* Example: add a custom theme
399+
ocean: {
400+
'--body-bg': 'radial-gradient(#0d2137, #061220)',
401+
'--msger-bg': 'radial-gradient(#0d2137, #061220)',
402+
'--msger-private-bg': 'radial-gradient(#0d2137, #061220)',
403+
'--wb-bg': 'radial-gradient(#0d2137, #061220)',
404+
'--elem-border-color': '1px solid rgba(56, 189, 248, 0.15)',
405+
'--navbar-bg': 'rgba(6, 18, 32, 0.88)',
406+
'--select-bg': '#0f2a45',
407+
'--tab-btn-active': '#163d5e',
408+
'--box-shadow': '0px 4px 12px 0px rgba(0, 0, 0, 0.5)',
409+
'--left-msg-bg': '#112d4a',
410+
'--right-msg-bg': '#0a1f35',
411+
'--private-msg-bg': '#0e2540',
412+
'--btn-bar-bg-color': '#E0F2FE',
413+
'--btn-bar-color': '#061220',
414+
'--btns-bg-color': 'rgba(6, 18, 32, 0.75)',
415+
'--dd-color': '#38BDF8',
416+
},
417+
*/
418+
},
382419
/**
383420
* Configuration for controlling the visibility of buttons in the MiroTalk P2P client.
384421
* Set properties to true to show the corresponding buttons, or false to hide them.

app/src/server.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ dependencies: {
4545
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
4646
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
4747
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
48-
* @version 1.7.92
48+
* @version 1.7.93
4949
*
5050
*/
5151

@@ -783,6 +783,11 @@ app.get('/buttons', (req, res) => {
783783
res.status(200).json({ message: config.buttons ? config.buttons : false });
784784
});
785785

786+
// UI themes configuration
787+
app.get('/themes', (req, res) => {
788+
res.status(200).json({ message: config.themes ? config.themes : false });
789+
});
790+
786791
// UI brand configuration
787792
app.get('/brand', (req, res) => {
788793
res.status(200).json({ message: config.brand && brandHtmlInjection ? config.brand : false });

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mirotalk",
3-
"version": "1.7.92",
3+
"version": "1.7.93",
44
"description": "A free WebRTC browser-based video call",
55
"main": "server.js",
66
"scripts": {

public/css/client.css

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@
66

77
:root {
88
/* common */
9-
--navbar-bg: rgba(0, 0, 0, 0.2);
10-
--body-bg: radial-gradient(#393939, #000000);
119
--border: 0.5px solid rgb(255 255 255 / 18%);
12-
--dd-color: #ffffff;
1310

1411
--msger-top: 50%;
1512
--msger-left: 50%;
@@ -37,14 +34,6 @@
3734
--btns-hover-scale: scale(1.1);
3835
--hover-color: grey;
3936

40-
/* video elem border color */
41-
--elem-border-color: none;
42-
43-
--tab-btn-active: rgb(30 29 29);
44-
45-
--btn-bar-bg-color: #ffffff;
46-
--btn-bar-color: #000000;
47-
4837
/* bottom buttons bar horizontal default */
4938
--bottom-btns-top: auto;
5039
--bottom-btns-left: 50%;
@@ -54,27 +43,31 @@
5443
--bottom-btns-margin-bottom: 18px;
5544
--bottom-btns-flex-direction: row;
5645

57-
/* dark theme */
58-
--body-bg: radial-gradient(#393939, #000000);
59-
--msger-bg: radial-gradient(#393939, #000000);
60-
--msger-private-bg: radial-gradient(#393939, #000000);
61-
--wb-bg: radial-gradient(#393939, #000000);
62-
--select-bg: #2c2c2c;
63-
--box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
64-
65-
/* chat msg boble */
66-
--left-msg-bg: #252d31;
67-
--right-msg-bg: #056162;
68-
--private-msg-bg: #6b1226;
46+
/* dark theme fallback (default) */
47+
--body-bg: radial-gradient(#2a2a2e, #121214);
48+
--msger-bg: radial-gradient(#2a2a2e, #121214);
49+
--msger-private-bg: radial-gradient(#2a2a2e, #121214);
50+
--wb-bg: radial-gradient(#2a2a2e, #121214);
51+
--navbar-bg: rgba(18, 18, 20, 0.85);
52+
--elem-border-color: 1px solid rgba(255, 255, 255, 0.08);
53+
--select-bg: #333338;
54+
--tab-btn-active: #3d3d42;
55+
--box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.5);
56+
--left-msg-bg: #2c2c30;
57+
--right-msg-bg: #3a3a40;
58+
--private-msg-bg: #252528;
59+
--btn-bar-bg-color: #e8e8ec;
60+
--btn-bar-color: #121214;
61+
--btns-bg-color: rgba(18, 18, 20, 0.75);
62+
--dd-color: #e8e8ec;
6963

7064
/* volume detected */
7165
--volume-up: 0px 8px 16px 0px rgb(249, 253, 0);
7266

7367
/* video avatar img size */
7468
--vmi-wh: 15vw;
75-
/*
76-
https://developer.mozilla.org/it/docs/Web/CSS/object-fit
77-
*/
69+
70+
/* https://developer.mozilla.org/it/docs/Web/CSS/object-fit */
7871
--video-object-fit: cover;
7972
}
8073

@@ -2806,6 +2799,11 @@ button {
28062799
box-shadow: 0 0 12px rgba(212, 184, 92, 0.2);
28072800
}
28082801

2802+
/* Dynamic server-added theme cards: active state handled via inline styles */
2803+
.theme-card.active i {
2804+
filter: brightness(1.2);
2805+
}
2806+
28092807
@media screen and (max-width: 500px) {
28102808
.theme-cards-grid {
28112809
grid-template-columns: repeat(3, 1fr);

public/js/brand.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ let brand = {
107107
},
108108
about: {
109109
imageUrl: '../images/mirotalk-logo.gif',
110-
title: 'WebRTC P2P v1.7.92',
110+
title: 'WebRTC P2P v1.7.93',
111111
html: `
112112
<button
113113
id="support-button"

public/js/client.js

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
1616
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
1717
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
18-
* @version 1.7.92
18+
* @version 1.7.93
1919
*
2020
*/
2121

@@ -1339,7 +1339,8 @@ function initCursorLightEffect() {
13391339
/**
13401340
* On body load Get started
13411341
*/
1342-
function initClientPeer() {
1342+
async function initClientPeer() {
1343+
await getThemes();
13431344
setTheme();
13441345

13451346
if (!isWebRTCSupported) {
@@ -1711,6 +1712,99 @@ async function getButtons() {
17111712
}
17121713
}
17131714

1715+
/**
1716+
* Get Themes config from server side and merge with built-in defaults
1717+
*/
1718+
async function getThemes() {
1719+
try {
1720+
const response = await axios.get('/themes', {
1721+
timeout: 5000,
1722+
});
1723+
const serverThemes = response.data.message;
1724+
if (serverThemes) {
1725+
// Deep merge each theme: server overrides take precedence
1726+
for (const [name, vars] of Object.entries(serverThemes)) {
1727+
themeMap[name] = themeMap[name] ? { ...themeMap[name], ...vars } : vars;
1728+
}
1729+
renderDynamicThemeCards();
1730+
console.log('AXIOS ROOM THEMES SETTINGS', {
1731+
serverThemes: serverThemes,
1732+
clientThemes: Object.keys(themeMap),
1733+
});
1734+
}
1735+
} catch (error) {
1736+
console.error('AXIOS GET THEMES ERROR', error.message);
1737+
}
1738+
}
1739+
1740+
/**
1741+
* Dynamically add theme cards & dropdown options for server-defined themes
1742+
* that are not part of the built-in defaults.
1743+
*/
1744+
function renderDynamicThemeCards() {
1745+
const grid = getId('themeCardsGrid');
1746+
if (!grid) return;
1747+
1748+
const builtInThemes = new Set(Array.from(themeSelect.options).map((opt) => opt.value));
1749+
1750+
const iconPool = [
1751+
'fa-solid fa-wand-magic-sparkles',
1752+
'fa-solid fa-palette',
1753+
'fa-solid fa-paint-roller',
1754+
'fa-solid fa-swatchbook',
1755+
'fa-solid fa-brush',
1756+
'fa-solid fa-eye-dropper',
1757+
'fa-solid fa-fill-drip',
1758+
'fa-solid fa-circle-half-stroke',
1759+
];
1760+
let iconIndex = 0;
1761+
1762+
for (const [name, vars] of Object.entries(themeMap)) {
1763+
if (builtInThemes.has(name)) continue;
1764+
1765+
// Add <option> to the hidden select
1766+
const option = document.createElement('option');
1767+
option.value = name;
1768+
option.textContent = name.charAt(0).toUpperCase() + name.slice(1);
1769+
themeSelect.appendChild(option);
1770+
1771+
const index = themeSelect.options.length - 1;
1772+
1773+
// Pick an icon and a color from the theme's --dd-color
1774+
const iconClass = iconPool[iconIndex % iconPool.length];
1775+
iconIndex++;
1776+
const iconColor = vars['--dd-color'] || '#c0c0c0';
1777+
1778+
// Create the card
1779+
const card = document.createElement('div');
1780+
card.className = 'theme-card';
1781+
card.dataset.theme = name;
1782+
card.dataset.index = index;
1783+
card.innerHTML = `<i class="${iconClass}"></i><span>${option.textContent}</span>`;
1784+
1785+
// Apply dynamic icon color via inline style
1786+
const icon = card.querySelector('i');
1787+
icon.style.color = iconColor;
1788+
1789+
// Set dynamic active border color
1790+
card.style.setProperty('--dynamic-theme-color', iconColor);
1791+
1792+
// Click handler (same logic as built-in cards)
1793+
card.onclick = () => {
1794+
if (card.classList.contains('disabled')) return;
1795+
themeSelect.selectedIndex = index;
1796+
updateThemeCardsActive();
1797+
if (themeCardDebounce) clearTimeout(themeCardDebounce);
1798+
themeCardDebounce = setTimeout(() => {
1799+
themeCardDebounce = null;
1800+
themeSelect.dispatchEvent(new Event('change'));
1801+
}, 200);
1802+
};
1803+
1804+
grid.appendChild(card);
1805+
}
1806+
}
1807+
17141808
/**
17151809
* Deep merge two objects
17161810
* @param {object} target target object
@@ -3116,7 +3210,7 @@ function handleRemovePeer(config) {
31163210
/**
31173211
* Theme definitions — each key maps to the CSS custom properties for that theme
31183212
*/
3119-
const themeMap = {
3213+
let themeMap = {
31203214
dark: {
31213215
'--body-bg': 'radial-gradient(#2a2a2e, #121214)',
31223216
'--msger-bg': 'radial-gradient(#2a2a2e, #121214)',
@@ -3328,7 +3422,14 @@ function setTheme() {
33283422
function updateThemeCardsActive() {
33293423
const cards = document.querySelectorAll('.theme-card');
33303424
cards.forEach((card) => {
3331-
card.classList.toggle('active', parseInt(card.dataset.index) === themeSelect.selectedIndex);
3425+
const isActive = parseInt(card.dataset.index) === themeSelect.selectedIndex;
3426+
card.classList.toggle('active', isActive);
3427+
// For dynamic (server-added) cards, apply active border/shadow via inline style
3428+
const dynamicColor = card.style.getPropertyValue('--dynamic-theme-color');
3429+
if (dynamicColor) {
3430+
card.style.borderColor = isActive ? dynamicColor : '';
3431+
card.style.boxShadow = isActive ? `0 0 12px ${dynamicColor}33` : '';
3432+
}
33323433
});
33333434
}
33343435

@@ -14836,7 +14937,7 @@ function showAbout() {
1483614937
Swal.fire({
1483714938
background: swBg,
1483814939
position: 'center',
14839-
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.7.92',
14940+
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.7.93',
1484014941
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
1484114942
customClass: { image: 'img-about' },
1484214943
html: `

0 commit comments

Comments
 (0)