[mirotalk] - improve collaborative whiteboard, update dep

This commit is contained in:
Miroslav Pejic
2025-12-02 21:40:26 +01:00
parent ce21448e55
commit f7832440e9
9 changed files with 696 additions and 49 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# ====================================================
# MiroTalk P2P v.1.6.65 - Environment Configuration
# MiroTalk P2P v.1.6.66 - Environment Configuration
# ====================================================
# App environment
+1 -1
View File
@@ -2,7 +2,7 @@
/**
* ==============================================
* MiroTalk P2P v.1.6.65 - Configuration File
* MiroTalk P2P v.1.6.66 - Configuration File
* ==============================================
*
* Branding and customizations require a license:
+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.6.65
* @version 1.6.66
*
*/
+27 -26
View File
@@ -1,17 +1,17 @@
{
"name": "mirotalk",
"version": "1.6.65",
"version": "1.6.66",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mirotalk",
"version": "1.6.65",
"version": "1.6.66",
"license": "AGPL-3.0",
"dependencies": {
"@mattermost/client": "11.1.0",
"@ngrok/ngrok": "1.6.0",
"@sentry/node": "^10.27.0",
"@sentry/node": "^10.28.0",
"axios": "^1.13.2",
"chokidar": "^5.0.0",
"colors": "^1.4.0",
@@ -20,7 +20,7 @@
"crypto-js": "^4.2.0",
"dompurify": "^3.3.0",
"dotenv": "^17.2.3",
"express": "^5.1.0",
"express": "^5.2.1",
"express-openid-connect": "^2.19.3",
"express-rate-limit": "^8.2.1",
"he": "^1.2.0",
@@ -1104,18 +1104,18 @@
"license": "Apache-2.0"
},
"node_modules/@sentry/core": {
"version": "10.27.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.27.0.tgz",
"integrity": "sha512-Zc68kdH7tWTDtDbV1zWIbo3Jv0fHAU2NsF5aD2qamypKgfSIMSbWVxd22qZyDBkaX8gWIPm/0Sgx6aRXRBXrYQ==",
"version": "10.28.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.28.0.tgz",
"integrity": "sha512-9yFIPxyfWkDzt+IaRjboeNiXOKi22ZRGG3ELmZlLak8JCC+vA+q/+AmF/8Jnw59WlL3/KVC1Q8+t8bLCkxlswg==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/node": {
"version": "10.27.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.27.0.tgz",
"integrity": "sha512-1cQZ4+QqV9juW64Jku1SMSz+PoZV+J59lotz4oYFvCNYzex8hRAnDKvNiKW1IVg5mEEkz98mg1fvcUtiw7GTiQ==",
"version": "10.28.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.28.0.tgz",
"integrity": "sha512-aih3iqagUU/9Xa6RObgdS9cKL3q5eerYNMJoO9SflMgeyhHBM5BRqo0IPSMQ9nuogrDBp443sgtW450VXYO7Bg==",
"license": "MIT",
"dependencies": {
"@opentelemetry/api": "^1.9.0",
@@ -1148,9 +1148,9 @@
"@opentelemetry/sdk-trace-base": "^2.2.0",
"@opentelemetry/semantic-conventions": "^1.37.0",
"@prisma/instrumentation": "6.19.0",
"@sentry/core": "10.27.0",
"@sentry/node-core": "10.27.0",
"@sentry/opentelemetry": "10.27.0",
"@sentry/core": "10.28.0",
"@sentry/node-core": "10.28.0",
"@sentry/opentelemetry": "10.28.0",
"import-in-the-middle": "^2",
"minimatch": "^9.0.0"
},
@@ -1159,14 +1159,14 @@
}
},
"node_modules/@sentry/node-core": {
"version": "10.27.0",
"resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.27.0.tgz",
"integrity": "sha512-Dzo1I64Psb7AkpyKVUlR9KYbl4wcN84W4Wet3xjLmVKMgrCo2uAT70V4xIacmoMH5QLZAx0nGfRy9yRCd4nzBg==",
"version": "10.28.0",
"resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.28.0.tgz",
"integrity": "sha512-OOmNtMSPHjiVb+dmTC9Lq+uIrC2FplZSdst033mH+ucBF7xjyY1/WAk02pw+hqNVFQKwaItqhGNFTmC7aST60Q==",
"license": "MIT",
"dependencies": {
"@apm-js-collab/tracing-hooks": "^0.3.1",
"@sentry/core": "10.27.0",
"@sentry/opentelemetry": "10.27.0",
"@sentry/core": "10.28.0",
"@sentry/opentelemetry": "10.28.0",
"import-in-the-middle": "^2"
},
"engines": {
@@ -1183,12 +1183,12 @@
}
},
"node_modules/@sentry/opentelemetry": {
"version": "10.27.0",
"resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.27.0.tgz",
"integrity": "sha512-z2vXoicuGiqlRlgL9HaYJgkin89ncMpNQy0Kje6RWyhpzLe8BRgUXlgjux7WrSrcbopDdC1OttSpZsJ/Wjk7fg==",
"version": "10.28.0",
"resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.28.0.tgz",
"integrity": "sha512-SiSLN294vlxipDG0/FvMYIFmyXEffXmPvvdyp5DUqY8NyJytYPPUJ3DuQhc9XRVyEd9XeOgra661nxNIKPr1pg==",
"license": "MIT",
"dependencies": {
"@sentry/core": "10.27.0"
"@sentry/core": "10.28.0"
},
"engines": {
"node": ">=18"
@@ -2736,19 +2736,20 @@
}
},
"node_modules/express": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
"body-parser": "^2.2.1",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"depd": "^2.0.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "mirotalk",
"version": "1.6.65",
"version": "1.6.66",
"description": "A free WebRTC browser-based video call",
"main": "server.js",
"scripts": {
@@ -43,7 +43,7 @@
"dependencies": {
"@mattermost/client": "11.1.0",
"@ngrok/ngrok": "1.6.0",
"@sentry/node": "^10.27.0",
"@sentry/node": "^10.28.0",
"axios": "^1.13.2",
"chokidar": "^5.0.0",
"colors": "^1.4.0",
@@ -52,7 +52,7 @@
"crypto-js": "^4.2.0",
"dompurify": "^3.3.0",
"dotenv": "^17.2.3",
"express": "^5.1.0",
"express": "^5.2.1",
"express-openid-connect": "^2.19.3",
"express-rate-limit": "^8.2.1",
"he": "^1.2.0",
+195
View File
@@ -186,3 +186,198 @@
background: transparent;
padding: 0;
}
/* Whiteboard Shortcuts Styles */
#whiteboardShortcutsContent {
display: none;
}
.wb-shortcuts-container {
text-align: left;
font-family: 'Segoe UI', Arial, sans-serif;
background: var(--body-bg);
padding: 1.5rem;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
max-width: 600px;
margin: 0 auto;
}
.wb-shortcuts-title {
margin-top: 1.5rem;
margin-bottom: 0.8rem;
font-size: 1.3rem;
font-weight: 700;
color: #ffd700;
display: flex;
align-items: center;
gap: 0.6rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid rgba(255, 215, 0, 0.3);
}
.wb-shortcuts-title:first-child {
margin-top: 0;
}
.wb-shortcuts-title i {
color: #ffa500;
font-size: 1.2rem;
}
.wb-shortcuts-code {
background: var(--body-bg);
padding: 1rem 1rem;
border-radius: 8px;
white-space: pre;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 0.95rem;
line-height: 1.8;
color: var(--text-color);
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
filter: brightness(0.85);
}
.wb-shortcuts-text {
color: #66beff;
font-weight: 700;
font-size: 1.1rem;
letter-spacing: 0.03em;
background: linear-gradient(135deg, rgba(102, 190, 255, 0.15), rgba(102, 190, 255, 0.08));
padding: 8px 16px;
border-radius: 8px;
display: inline-block;
box-shadow: 0 2px 8px rgba(102, 190, 255, 0.1);
}
.wb-shortcuts-list {
list-style: none;
padding-left: 0;
background: var(--body-bg);
border-radius: 8px;
padding: 0.8rem;
filter: brightness(0.9);
}
.wb-shortcuts-list li {
margin: 0.6rem 0;
padding: 0.5rem 0.8rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 6px;
color: var(--text-color);
font-weight: 500;
font-size: 1rem;
transition: all 0.2s ease;
}
.wb-shortcuts-list li:hover {
background: rgba(255, 215, 0, 0.15);
border-left: 3px solid #ffd700;
}
/* Sticky Note Dialog Styles */
.sticky-note-form {
display: flex;
background: var(--body-bg);
flex-direction: column;
gap: 1.2rem;
padding: 1.2rem;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
border: 1px solid rgba(255, 215, 0, 0.18);
max-width: 100%;
box-sizing: border-box;
}
.sticky-note-colors-row {
display: flex;
gap: 1rem;
width: 100%;
flex-wrap: wrap;
}
.sticky-note-textarea {
width: 100%;
padding: 12px;
border: 2px solid rgba(255, 215, 0, 0.3);
border-radius: 10px;
background: rgba(255, 235, 59, 0.07);
color: var(--text-color, #ffffff);
font-size: 1rem;
font-family: 'Segoe UI', Arial, sans-serif;
resize: vertical;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(255, 215, 0, 0.08);
box-sizing: border-box;
}
.sticky-note-color-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
flex: 1;
min-width: 120px;
}
.sticky-note-color-label {
font-weight: 600;
font-size: 0.95rem;
color: var(--text-color, #ffffff);
display: flex;
align-items: center;
gap: 0.5rem;
}
.sticky-note-color-input {
width: 100%;
height: 50px;
border: none !important;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 1px 4px rgba(255, 215, 0, 0.07);
}
.sticky-note-color-input:hover {
transform: scale(1.01);
}
.sticky-note-color-input::-webkit-color-swatch-wrapper {
padding: 4px;
border-radius: 10px;
}
.sticky-note-color-input::-webkit-color-swatch {
border: none;
border-radius: 8px;
}
.sticky-note-color-input::-moz-color-swatch {
border: none;
border-radius: 8px;
}
/* Responsive styles for small screens */
@media (max-width: 600px) {
.sticky-note-form {
padding: 0.7rem;
border-radius: 8px;
gap: 0.7rem;
}
.sticky-note-colors-row {
flex-direction: column;
gap: 0.7rem;
}
.sticky-note-color-group {
min-width: 0;
}
.sticky-note-textarea {
font-size: 0.95rem;
padding: 8px;
}
.sticky-note-color-input {
height: 38px;
border-radius: 8px;
}
}
+1 -1
View File
@@ -77,7 +77,7 @@ let brand = {
},
about: {
imageUrl: '../images/mirotalk-logo.gif',
title: 'WebRTC P2P v1.6.65',
title: 'WebRTC P2P v1.6.66',
html: `
<button
id="support-button"
+429 -15
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.6.65
* @version 1.6.66
*
*/
@@ -391,7 +391,9 @@ const wbDrawingColorEl = getId('wbDrawingColorEl');
const whiteboardGhostButton = getId('whiteboardGhostButton');
const wbBackgroundColorEl = getId('wbBackgroundColorEl');
const whiteboardPencilBtn = getId('whiteboardPencilBtn');
const whiteboardVanishingBtn = getId('whiteboardVanishingBtn');
const whiteboardObjectBtn = getId('whiteboardObjectBtn');
const whiteboardEraserBtn = getId('whiteboardEraserBtn');
const whiteboardUndoBtn = getId('whiteboardUndoBtn');
const whiteboardRedoBtn = getId('whiteboardRedoBtn');
const whiteboardDropDownMenuBtn = getId('whiteboardDropDownMenuBtn');
@@ -400,16 +402,18 @@ const whiteboardImgFileBtn = getId('whiteboardImgFileBtn');
const whiteboardPdfFileBtn = getId('whiteboardPdfFileBtn');
const whiteboardImgUrlBtn = getId('whiteboardImgUrlBtn');
const whiteboardTextBtn = getId('whiteboardTextBtn');
const whiteboardStickyNoteBtn = getId('whiteboardStickyNoteBtn');
const whiteboardLineBtn = getId('whiteboardLineBtn');
const whiteboardRectBtn = getId('whiteboardRectBtn');
const whiteboardTriangleBtn = getId('whiteboardTriangleBtn');
const whiteboardCircleBtn = getId('whiteboardCircleBtn');
const whiteboardSaveBtn = getId('whiteboardSaveBtn');
const whiteboardEraserBtn = getId('whiteboardEraserBtn');
const whiteboardCleanBtn = getId('whiteboardCleanBtn');
const whiteboardLockBtn = getId('whiteboardLockBtn');
const whiteboardUnlockBtn = getId('whiteboardUnlockBtn');
const whiteboardCloseBtn = getId('whiteboardCloseBtn');
const whiteboardShortcutsBtn = getId('whiteboardShortcutsBtn');
const whiteboardShortcutsContent = getId('whiteboardShortcutsContent');
// Room actions buttons
const captionEveryoneBtn = getId('captionEveryoneBtn');
@@ -661,8 +665,10 @@ let wbIsDrawing = false;
let wbIsOpen = false;
let wbIsRedoing = false;
let wbIsEraser = false;
let wbIsVanishing = false;
let wbIsBgTransparent = false;
let wbPop = [];
let wbVanishingObjects = [];
let isWhiteboardFs = false;
// file transfer
@@ -824,7 +830,9 @@ function setButtonsToolTip() {
setTippy(whiteboardGhostButton, 'Toggle transparent background', 'bottom');
setTippy(wbBackgroundColorEl, 'Background color', 'bottom');
setTippy(whiteboardPencilBtn, 'Drawing mode', 'bottom');
setTippy(whiteboardVanishingBtn, 'Vanishing pen (disappears in 5s)', 'bottom');
setTippy(whiteboardObjectBtn, 'Object mode', 'bottom');
setTippy(whiteboardEraserBtn, 'Eraser mode', 'bottom');
setTippy(whiteboardUndoBtn, 'Undo', 'bottom');
setTippy(whiteboardRedoBtn, 'Redo', 'bottom');
// Suspend/Hide File transfer buttons
@@ -5923,6 +5931,12 @@ function setMyWhiteboardBtn() {
whiteboardObjectBtn.addEventListener('click', (e) => {
whiteboardIsDrawingMode(false);
});
whiteboardStickyNoteBtn.addEventListener('click', (e) => {
whiteboardAddObj('stickyNote');
});
whiteboardVanishingBtn.addEventListener('click', (e) => {
whiteboardIsVanishingMode(true);
});
whiteboardUndoBtn.addEventListener('click', (e) => {
whiteboardAction(getWhiteboardAction('undo'));
});
@@ -5988,6 +6002,10 @@ function setMyWhiteboardBtn() {
//setWhiteboardBgColor(wbIsBgTransparent ? 'rgba(0, 0, 0, 0.100)' : wbBackgroundColorEl.value);
wbIsBgTransparent ? wbCanvasBackgroundColor('rgba(0, 0, 0, 0.100)') : setTheme();
});
whiteboardShortcutsBtn.addEventListener('click', (e) => {
showWhiteboardShortcuts();
});
// Hide the whiteboard dropdown menu if clicked outside
document.addEventListener('click', (event) => {
if (!whiteboardDropDownMenuBtn.contains(event.target) && !whiteboardDropDownMenuBtn.contains(event.target)) {
@@ -11177,7 +11195,9 @@ function toggleWhiteboard() {
function setupWhiteboard() {
setupWhiteboardCanvas();
setupWhiteboardCanvasSize();
setupWhiteboardLocalListners();
setupWhiteboardLocalListeners();
setupWhiteboardShortcuts();
setupWhiteboardDragAndDrop();
}
/**
@@ -11249,11 +11269,34 @@ function whiteboardIsDrawingMode(status) {
wbCanvas.isDrawingMode = status;
if (status) {
setColor(whiteboardPencilBtn, 'green');
setColor(whiteboardVanishingBtn, 'white');
setColor(whiteboardObjectBtn, 'white');
setColor(whiteboardEraserBtn, 'white');
wbIsEraser = false;
wbIsVanishing = false;
} else {
setColor(whiteboardPencilBtn, 'white');
setColor(whiteboardVanishingBtn, 'white');
setColor(whiteboardObjectBtn, 'green');
}
}
/**
* Whiteboard: vanishing mode
* @param {boolean} status if vanishing mode on
*/
function whiteboardIsVanishingMode(status) {
wbCanvas.isDrawingMode = status;
wbIsVanishing = status;
if (status) {
setColor(whiteboardVanishingBtn, 'green');
setColor(whiteboardPencilBtn, 'white');
setColor(whiteboardObjectBtn, 'white');
setColor(whiteboardEraserBtn, 'white');
wbIsEraser = false;
} else {
setColor(whiteboardPencilBtn, 'white');
setColor(whiteboardVanishingBtn, 'white');
wbCanvas.isDrawingMode = false;
setColor(whiteboardObjectBtn, 'green');
}
}
@@ -11263,9 +11306,18 @@ function whiteboardIsDrawingMode(status) {
* @param {boolean} status if eraser on
*/
function whiteboardIsEraser(status) {
whiteboardIsDrawingMode(false);
if (status) {
wbCanvas.isDrawingMode = false;
wbIsVanishing = false;
setColor(whiteboardPencilBtn, 'white');
setColor(whiteboardVanishingBtn, 'white');
setColor(whiteboardObjectBtn, 'white');
setColor(whiteboardEraserBtn, 'green');
} else {
setColor(whiteboardEraserBtn, 'white');
setColor(whiteboardObjectBtn, 'green');
}
wbIsEraser = status;
setColor(whiteboardEraserBtn, wbIsEraser ? 'green' : 'white');
}
/**
@@ -11311,6 +11363,9 @@ function whiteboardAddObj(type) {
case 'pdfFile':
setupFileSelection('Select the PDF', wbPdfInput, renderPdfToCanvas);
break;
case 'stickyNote':
createStickyNote();
break;
case 'text':
const text = new fabric.IText('Lorem Ipsum', {
top: 0,
@@ -11370,6 +11425,175 @@ function whiteboardAddObj(type) {
}
}
function whiteboardEraseObject() {
if (wbCanvas && typeof wbCanvas.getActiveObjects === 'function') {
const activeObjects = wbCanvas.getActiveObjects();
if (activeObjects && activeObjects.length > 0) {
// Remove all selected objects
activeObjects.forEach((obj) => {
wbCanvas.remove(obj);
});
wbCanvas.discardActiveObject();
wbCanvas.requestRenderAll();
wbCanvasToJson();
}
}
}
function whiteboardCloneObject() {
if (wbCanvas && typeof wbCanvas.getActiveObjects === 'function') {
const activeObjects = wbCanvas.getActiveObjects();
if (activeObjects && activeObjects.length > 0) {
activeObjects.forEach((obj, idx) => {
obj.clone((cloned) => {
// Offset each clone for visibility
cloned.set({
left: obj.left + 30 + idx * 10,
top: obj.top + 30 + idx * 10,
evented: true,
});
wbCanvas.add(cloned);
wbCanvas.setActiveObject(cloned);
wbCanvasToJson();
});
});
wbCanvas.requestRenderAll();
}
}
}
function wbHandleVanishingObjects() {
if (wbIsVanishing && wbCanvas._objects.length > 0) {
const obj = wbCanvas._objects[wbCanvas._objects.length - 1];
if (obj && obj.type === 'path') {
wbVanishingObjects.push(obj);
const fadeDuration = 1000,
vanishTimeout = 5000;
setTimeout(() => {
const start = performance.now();
function fade(ts) {
const p = Math.min((ts - start) / fadeDuration, 1);
obj.set('opacity', 1 - p);
wbCanvas.requestRenderAll();
if (p < 1) requestAnimationFrame(fade);
}
requestAnimationFrame(fade);
}, vanishTimeout - fadeDuration);
setTimeout(() => {
wbCanvas.remove(obj);
wbCanvas.renderAll();
wbCanvasToJson();
wbVanishingObjects.splice(wbVanishingObjects.indexOf(obj), 1);
}, vanishTimeout);
}
}
}
function createStickyNote() {
Swal.fire({
background: swBg,
title: 'Create Sticky Note',
html: `
<div class="sticky-note-form">
<textarea id="stickyNoteText" class="sticky-note-textarea" rows="4" placeholder="Type your note here...">Note</textarea>
<div class="sticky-note-colors-row">
<div class="sticky-note-color-group">
<label for="stickyNoteColor" class="sticky-note-color-label">
<i class="fas fa-palette"></i> Background
</label>
<input id="stickyNoteColor" type="color" value="#FFEB3B" class="sticky-note-color-input">
</div>
<div class="sticky-note-color-group">
<label for="stickyNoteTextColor" class="sticky-note-color-label">
<i class="fas fa-font"></i> Text
</label>
<input id="stickyNoteTextColor" type="color" value="#000000" class="sticky-note-color-input">
</div>
</div>
</div>
`,
showCancelButton: true,
confirmButtonText: 'Create',
cancelButtonText: 'Cancel',
showClass: { popup: 'animate__animated animate__fadeInDown' },
hideClass: { popup: 'animate__animated animate__fadeOutUp' },
preConfirm: () => {
return {
text: getId('stickyNoteText').value,
color: getId('stickyNoteColor').value,
textColor: getId('stickyNoteTextColor').value,
};
},
didOpen: () => {
// Focus textarea for quick typing
setTimeout(() => {
getId('stickyNoteText').focus();
}, 100);
},
}).then((result) => {
if (result.isConfirmed) {
const noteData = result.value;
// Create sticky note background (rectangle)
const noteRect = new fabric.Rect({
left: 100,
top: 100,
width: 220,
height: 160,
fill: noteData.color,
shadow: 'rgba(0,0,0,0.18) 0px 4px 12px',
rx: 14,
ry: 14,
});
// Create text for sticky note
const noteText = new fabric.Textbox(noteData.text, {
left: 110,
top: 110,
width: 200,
fontSize: 18,
fontFamily: 'Segoe UI, Arial, sans-serif',
fill: noteData.textColor,
textAlign: 'left',
editable: true,
fontWeight: 'bold',
shadow: new fabric.Shadow({
color: 'rgba(255,255,255,0.18)',
blur: 2,
offsetX: 1,
offsetY: 1,
}),
padding: 8,
cornerSize: 8,
});
// Group rectangle and text together
const stickyNoteGroup = new fabric.Group([noteRect, noteText], {
left: 100,
top: 100,
selectable: true,
hasControls: true,
hoverCursor: 'pointer',
});
// Make the text editable by handling double-click events
stickyNoteGroup.on('mousedblclick', function () {
noteText.enterEditing();
noteText.hiddenTextarea && noteText.hiddenTextarea.focus();
});
// Exit editing when clicking outside the noteText
wbCanvas.on('mouse:down', function (e) {
if (noteText.isEditing && e.target !== noteText) {
noteText.exitEditing();
}
});
addWbCanvasObj(stickyNoteGroup);
}
});
}
/**
* Setup Canvas file selections
* @param {string} title
@@ -11569,13 +11793,15 @@ function addWbCanvasObj(obj) {
wbCanvas.add(obj).setActiveObject(obj);
whiteboardIsDrawingMode(false);
wbCanvasToJson();
} else {
console.error('Invalid input. Expected an obj of canvas elements');
}
}
/**
* Whiteboard: Local listners
*/
function setupWhiteboardLocalListners() {
function setupWhiteboardLocalListeners() {
wbCanvas.on('mouse:down', function (e) {
mouseDown(e);
});
@@ -11598,6 +11824,10 @@ function setupWhiteboardLocalListners() {
function mouseDown(e) {
wbIsDrawing = true;
if (wbIsEraser && e.target) {
// Don't add vanishing objects to redo stack
if (!wbVanishingObjects.includes(e.target)) {
wbPop.push(e.target); // To allow redo
}
wbCanvas.remove(e.target);
return;
}
@@ -11631,6 +11861,7 @@ function mouseMove() {
function objectAdded() {
if (!wbIsRedoing) wbPop = [];
wbIsRedoing = false;
wbHandleVanishingObjects();
}
/**
@@ -11649,7 +11880,11 @@ function wbCanvasBackgroundColor(color) {
*/
function wbCanvasUndo() {
if (wbCanvas._objects.length > 0) {
wbPop.push(wbCanvas._objects.pop());
const obj = wbCanvas._objects.pop();
// Don't add vanishing objects to redo stack
if (!wbVanishingObjects.includes(obj)) {
wbPop.push(obj);
}
wbCanvas.renderAll();
}
}
@@ -11727,12 +11962,13 @@ async function wbUpdate() {
* Whiteboard: json to canvas objects
* @param {object} config data
*/
function handleJsonToWbCanvas(config) {
function JsonToWbCanvas(config) {
if (!wbIsOpen) toggleWhiteboard();
wbCanvas.loadFromJSON(config.wbCanvasJson);
wbCanvas.renderAll();
wbIsRedoing = true;
wbCanvas.loadFromJSON(config.wbCanvasJson, function () {
wbCanvas.renderAll();
wbIsRedoing = false;
});
if (!isPresenter && !wbCanvas.isDrawingMode && wbIsLock) {
wbDrawing(false);
}
@@ -11760,7 +11996,7 @@ function confirmCleanBoard() {
Swal.fire({
background: swBg,
imageUrl: images.delete,
position: 'center',
position: 'top',
title: 'Clean the board',
text: 'Are you sure you want to clean the board?',
showDenyButton: true,
@@ -11810,6 +12046,7 @@ function handleWhiteboardAction(config, logMe = true) {
break;
case 'clear':
wbCanvas.clear();
wbCanvas.renderAll();
break;
case 'toggle':
toggleWhiteboard();
@@ -11850,6 +12087,183 @@ function wbDrawing(status) {
});
}
/**
* Show whiteboard shortcuts
*/
function showWhiteboardShortcuts() {
if (!whiteboardShortcutsContent) {
console.error('Whiteboard shortcuts content not found');
return;
}
Swal.fire({
background: swBg,
position: 'center',
title: 'Whiteboard Shortcuts',
html: whiteboardShortcutsContent.innerHTML,
confirmButtonText: 'Got it!',
showClass: { popup: 'animate__animated animate__fadeInDown' },
hideClass: { popup: 'animate__animated animate__fadeOutUp' },
});
}
/**
* Setup whiteboard drag and drop
* @returns void
*/
function setupWhiteboardDragAndDrop() {
if (!wbCanvas) return;
// Prevent default drag behaviors
['dragenter', 'dragover', 'dragleave', 'drop'].forEach((eventName) => {
wbCanvas.upperCanvasEl.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// Highlight drop area
['dragenter', 'dragover'].forEach((eventName) => {
wbCanvas.upperCanvasEl.addEventListener(
eventName,
() => {
wbCanvas.upperCanvasEl.style.border = '1px dashed #fff';
},
false
);
});
['dragleave', 'drop'].forEach((eventName) => {
wbCanvas.upperCanvasEl.addEventListener(
eventName,
() => {
wbCanvas.upperCanvasEl.style.border = '';
},
false
);
});
// Handle dropped files
wbCanvas.upperCanvasEl.addEventListener('drop', handleWhiteboardDrop, false);
}
/**
* Handle whiteboard drop
* @param {object} e event
*/
function handleWhiteboardDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length === 0) return;
const file = files[0];
const fileType = file.type;
switch (true) {
case fileType.startsWith('image/'):
renderImageToCanvas(file);
break;
case fileType === 'application/pdf':
renderPdfToCanvas(file);
break;
default:
userLog('warning', `Unsupported file type: ${fileType}. Please drop an image or PDF file.`, 6000);
break;
}
}
/**
* Setup whiteboard keyboard shortcuts
*/
function setupWhiteboardShortcuts() {
document.addEventListener('keydown', (event) => {
if (!wbIsOpen) return;
// Whiteboard clone shortcut: Cmd+C/Ctrl+C
if ((event.key === 'c' || event.key === 'C') && (event.ctrlKey || event.metaKey)) {
whiteboardCloneObject();
event.preventDefault();
return;
}
// Whiteboard erase shortcut: Cmd+X/Ctrl+X
if ((event.key === 'x' || event.key === 'X') && (event.ctrlKey || event.metaKey)) {
whiteboardEraseObject();
event.preventDefault();
return;
}
// Whiteboard undo shortcuts: Cmd+Z/Ctrl+Z
if ((event.key === 'z' || event.key === 'Z') && (event.ctrlKey || event.metaKey) && !event.shiftKey) {
whiteboardAction(getWhiteboardAction('undo'));
event.preventDefault();
return;
}
// Whiteboard Redo shortcuts: Cmd+Shift+Z/Ctrl+Shift+Z or Cmd+Y/Ctrl+Y
if (
((event.key === 'z' || event.key === 'Z') && (event.ctrlKey || event.metaKey) && event.shiftKey) ||
((event.key === 'y' || event.key === 'Y') && (event.ctrlKey || event.metaKey))
) {
whiteboardAction(getWhiteboardAction('redo'));
event.preventDefault();
return;
}
// Use event.code and check for Alt+Meta (Mac) or Alt+Ctrl (Windows/Linux)
if (event.code && event.altKey && (event.ctrlKey || event.metaKey) && !event.shiftKey) {
switch (event.code) {
case 'KeyT': // Text
whiteboardAddObj('text');
event.preventDefault();
break;
case 'KeyL': // Line
whiteboardAddObj('line');
event.preventDefault();
break;
case 'KeyC': // Circle
whiteboardAddObj('circle');
event.preventDefault();
break;
case 'KeyR': // Rectangle
whiteboardAddObj('rect');
event.preventDefault();
break;
case 'KeyG': // Triangle (G for Geometry)
whiteboardAddObj('triangle');
event.preventDefault();
break;
case 'KeyN': // Sticky Note
whiteboardAddObj('stickyNote');
event.preventDefault();
break;
case 'KeyU': // Image (from URL)
whiteboardAddObj('imgUrl');
event.preventDefault();
break;
case 'KeyV': // Vanishing Pen
whiteboardIsVanishingMode(!wbIsVanishing);
event.preventDefault();
break;
case 'KeyI': // Image (from file)
whiteboardAddObj('imgFile');
event.preventDefault();
break;
case 'KeyP': // PDF (from file)
whiteboardAddObj('pdfFile');
event.preventDefault();
break;
case 'KeyQ': // Clear Board
confirmCleanBoard();
event.preventDefault();
break;
default:
break;
}
}
});
}
/**
* Create File Sharing Data Channel
* @param {string} peer_id socket.id
@@ -12662,7 +13076,7 @@ function showAbout() {
Swal.fire({
background: swBg,
position: 'center',
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.6.65',
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.6.66',
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
customClass: { image: 'img-about' },
html: `
+38 -1
View File
@@ -962,7 +962,9 @@ access to use this app.
<input id="wbBackgroundColorEl" class="whiteboardColorPicker" type="color" value="#000000" />
<input id="wbDrawingColorEl" class="whiteboardColorPicker" type="color" value="#FFFFFF" />
<button id="whiteboardPencilBtn" class="fas fa-pencil-alt"></button>
<button id="whiteboardVanishingBtn" class="fas fa-highlighter"></button>
<button id="whiteboardObjectBtn" class="fas fa-mouse-pointer"></button>
<button id="whiteboardEraserBtn" class="fas fa-eraser"></button>
<button id="whiteboardUndoBtn" class="fas fa-undo"></button>
<button id="whiteboardRedoBtn" class="fas fa-redo"></button>
<div class="whiteboard-dropdown">
@@ -984,6 +986,9 @@ access to use this app.
<button id="whiteboardImgUrlBtn"><i class="fas fa-link"></i> Add image URL</button>
<button id="whiteboardPdfFileBtn"><i class="far fa-file-pdf"></i> Add pdf file</button>
<button id="whiteboardTextBtn"><i class="fas fa-spell-check"></i> Add text</button>
<button id="whiteboardStickyNoteBtn">
<i class="fas fa-sticky-note"></i> Add sticky note
</button>
<button id="whiteboardLineBtn"><i class="fas fa-slash"></i> Add line</button>
<button id="whiteboardRectBtn"><i class="far fa-square"></i> Add rectangle</button>
<button id="whiteboardTriangleBtn">
@@ -1000,14 +1005,46 @@ access to use this app.
</button>
<button id="whiteboardCircleBtn"><i class="far fa-circle"></i> Add circle</button>
<button id="whiteboardSaveBtn"><i class="fas fa-save"></i> Save</button>
<button id="whiteboardEraserBtn"><i class="fas fa-eraser"></i> Eraser</button>
<button id="whiteboardCleanBtn"><i class="fas fa-trash"></i> Clean</button>
<button id="whiteboardShortcutsBtn"><i class="fas fa-keyboard"></i> Shortcuts</button>
</div>
</div>
</div>
</header>
<main>
<canvas id="wbCanvas"></canvas>
<div id="whiteboardShortcutsContent">
<div class="wb-shortcuts-container">
<h3 class="wb-shortcuts-title"><i class="fas fa-keyboard"></i> General</h3>
<pre class="wb-shortcuts-code">
Clone: ⌘/Ctrl + C
Erase: ⌘/Ctrl + X
Undo: ⌘/Ctrl + Z
Redo: ⌘/Ctrl + Shift + Z or ⌘/Ctrl + Y</pre
>
<h3 class="wb-shortcuts-title"><i class="fas fa-pencil-alt"></i> Create Objects</h3>
<p class="wb-shortcuts-text"><strong>Hold:</strong></p>
<ul class="wb-shortcuts-list">
<li>• Alt + Cmd (Mac)</li>
<li>• Alt + Ctrl (Win/Linux)</li>
</ul>
<p class="wb-shortcuts-text"><strong>Then press:</strong></p>
<pre class="wb-shortcuts-code">
T = Text
L = Line
C = Circle
R = Rectangle
G = Triangle
N = Sticky Note
U = Image URL
I = Image
P = PDF
V = Vanishing Pen
Q = Clear all</pre
>
</div>
</div>
</main>
</section>