Chalkboard feature added.

This commit is contained in:
Storm Dragon
2026-05-24 22:36:27 -04:00
parent 0f5a19f31a
commit 0a149e51ef
9 changed files with 537 additions and 9 deletions
+142
View File
@@ -579,6 +579,7 @@ function gotClose(code, reason) {
closeUpMedia();
closeSafariStream();
setConnected(false);
resetChalkboard();
if(code != 1000) {
console.warn('Socket close', code, reason);
}
@@ -1802,6 +1803,19 @@ function userMenu(elt) {
serverConnection.userAction('identify', id);
}});
}
if(serverConnection.permissions.indexOf('op') >= 0 ||
serverConnection.permissions.indexOf('admin') >= 0) {
if(serverConnection.permissions.indexOf('op') < 0)
items.push({type: 'seperator'}); // sic
if(user.permissions.indexOf('chalkboard') >= 0)
items.push({label: 'Forbid chalkboard editing', onClick: () => {
serverConnection.userAction('unchalkboard', id);
}});
else
items.push({label: 'Allow chalkboard editing', onClick: () => {
serverConnection.userAction('chalkboard', id);
}});
}
}
openActionMenu(items, elt, `Actions for ${user.username || 'participant'}`);
}
@@ -1981,6 +1995,7 @@ function displayUsername() {
document.getElementById('userspan').textContent = serverConnection.username;
let op = serverConnection.permissions.indexOf('op') >= 0;
let present = serverConnection.permissions.indexOf('present') >= 0;
let chalkboard = canEditChalkboard();
let text = '';
if(op && present)
text = '(op, presenter)';
@@ -1988,6 +2003,8 @@ function displayUsername() {
text = 'operator';
else if(present)
text = 'presenter';
else if(chalkboard)
text = 'chalkboard editor';
document.getElementById('permspan').textContent = text;
}
@@ -2098,6 +2115,7 @@ async function gotJoined(kind, hall, perms, status, data, error, message) {
setTitle((status && status.displayName) || capitalise(hall));
setConnected(true);
displayUsername();
updateChalkboardAccess();
setButtonsVisibility();
setChangePassword(pwAuth && !!hallStatus.canChangePassword &&
serverConnection.username
@@ -2404,6 +2422,13 @@ function gotUserMessage(id, dest, username, time, privileged, kind, error, messa
clearChat(id, userId);
break;
}
case 'chalkboard':
if(!privileged) {
console.error(`Got unprivileged message of kind ${kind}`);
return;
}
applyChalkboardUpdate(message);
break;
case 'token':
if(!privileged) {
console.error(`Got unprivileged message of kind ${kind}`);
@@ -2586,6 +2611,89 @@ function announceChat(text) {
announceLiveRegion(text, 'chat-announcements');
}
let chalkboardRevision = 0;
let chalkboardSendTimer = null;
let applyingChalkboardUpdate = false;
function canEditChalkboard() {
if(!serverConnection || !serverConnection.permissions)
return false;
return serverConnection.permissions.indexOf('op') >= 0 ||
serverConnection.permissions.indexOf('admin') >= 0 ||
serverConnection.permissions.indexOf('chalkboard') >= 0;
}
function updateChalkboardAccess() {
let chalkboard = /** @type {HTMLTextAreaElement} */
(document.getElementById('chalkboard'));
if(!chalkboard)
return;
let canEdit = canEditChalkboard();
chalkboard.readOnly = !canEdit;
chalkboard.setAttribute('aria-readonly', canEdit ? 'false' : 'true');
chalkboard.setAttribute(
'aria-label',
canEdit ? 'Virtual chalkboard' : 'Virtual chalkboard, read only',
);
}
function resetChalkboard() {
chalkboardRevision = 0;
if(chalkboardSendTimer) {
clearTimeout(chalkboardSendTimer);
chalkboardSendTimer = null;
}
let chalkboard = /** @type {HTMLTextAreaElement} */
(document.getElementById('chalkboard'));
if(chalkboard)
chalkboard.value = '';
updateChalkboardAccess();
}
/**
* @param {any} message
*/
function applyChalkboardUpdate(message) {
if(!message || typeof message !== 'object') {
console.warn('Unexpected chalkboard message', message);
return;
}
let text = typeof message.text === 'string' ? message.text : '';
let revision = Number(message.revision || 0);
if(revision < chalkboardRevision)
return;
chalkboardRevision = revision;
let chalkboard = /** @type {HTMLTextAreaElement} */
(document.getElementById('chalkboard'));
if(!chalkboard)
return;
if(chalkboard.value !== text) {
applyingChalkboardUpdate = true;
chalkboard.value = text;
applyingChalkboardUpdate = false;
}
updateChalkboardAccess();
}
function scheduleChalkboardSend() {
if(!canEditChalkboard())
return;
if(chalkboardSendTimer)
clearTimeout(chalkboardSendTimer);
chalkboardSendTimer = setTimeout(() => {
chalkboardSendTimer = null;
let chalkboard = /** @type {HTMLTextAreaElement} */
(document.getElementById('chalkboard'));
if(!chalkboard || !serverConnection || !serverConnection.socket)
return;
serverConnection.hallAction('chalkboard', {
text: chalkboard.value,
revision: chalkboardRevision,
});
}, 350);
}
/**
* @param {string} text
*/
@@ -2866,6 +2974,14 @@ function operatorPredicate() {
return 'You are not an operator';
}
function chalkboardGrantPredicate() {
if(serverConnection && serverConnection.permissions &&
(serverConnection.permissions.indexOf('op') >= 0 ||
serverConnection.permissions.indexOf('admin') >= 0))
return null;
return 'You are not allowed to grant chalkboard editing';
}
function recordingPredicate() {
if(serverConnection && serverConnection.permissions &&
serverConnection.permissions.indexOf('record') >= 0)
@@ -3311,6 +3427,20 @@ commands.unshutup = {
f: userCommand,
};
commands.chalkboard = {
parameters: 'user',
description: 'give the right to edit the virtual chalkboard',
predicate: chalkboardGrantPredicate,
f: userCommand,
};
commands.unchalkboard = {
parameters: 'user',
description: 'revoke the right to edit the virtual chalkboard',
predicate: chalkboardGrantPredicate,
f: userCommand,
};
commands.mute = {
parameters: 'user',
description: 'mute a remote user',
@@ -3544,6 +3674,17 @@ document.getElementById('input').onkeypress = function(e) {
}
};
document.getElementById('chalkboard').addEventListener('input', function(e) {
if(applyingChalkboardUpdate)
return;
scheduleChalkboardSend();
});
document.getElementById('chalkboard').addEventListener('keydown', function(e) {
if(e.key === 'Tab')
return;
});
function updateChatResizerValue(leftPercent) {
let resizer = document.getElementById('resizer');
if(!resizer)
@@ -3981,4 +4122,5 @@ document.addEventListener('keydown', function(e) {
}
});
resetChalkboard();
start();