Chalkboard feature added.
This commit is contained in:
+1
-1
@@ -781,7 +781,7 @@ ServerConnection.prototype.chat = function(kind, dest, value) {
|
||||
/**
|
||||
* userAction sends a request to act on a user.
|
||||
*
|
||||
* @param {string} kind - One of "op", "unop", "kick", "present", "unpresent".
|
||||
* @param {string} kind - One of "op", "unop", "kick", "present", "unpresent", "chalkboard", "unchalkboard".
|
||||
* @param {string} dest - The id of the user to act upon.
|
||||
* @param {any} [value] - An action-dependent parameter.
|
||||
*/
|
||||
|
||||
+34
-1
@@ -731,6 +731,8 @@ h1 {
|
||||
#chatbox {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#chat {
|
||||
@@ -754,7 +756,8 @@ h1 {
|
||||
|
||||
#box {
|
||||
overflow: auto;
|
||||
height: calc(100% - 53px);
|
||||
flex: 1 1 auto;
|
||||
min-height: 6rem;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@@ -812,6 +815,36 @@ h1 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#chalkboard-area {
|
||||
border-top: 1px solid #d7d7d7;
|
||||
background: #fff;
|
||||
padding: 0.35rem;
|
||||
}
|
||||
|
||||
#chalkboard-label {
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
#chalkboard {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 8rem;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
#chalkboard[readonly] {
|
||||
background: #f3f3f3;
|
||||
}
|
||||
|
||||
#chalkboard:focus {
|
||||
outline: 2px solid #0066cc;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.sidenav {
|
||||
background-color: #4d076b;
|
||||
box-shadow: 0 0 24px 0 rgba(71,77,86,.1), 0 1px 0 0 rgba(71,77,86,.08);
|
||||
|
||||
@@ -76,6 +76,10 @@
|
||||
<input id="inputbutton" type="submit" value="➤" class="btn btn-default" aria-label="Send message"/>
|
||||
</form>
|
||||
</div>
|
||||
<div id="chalkboard-area">
|
||||
<label id="chalkboard-label" for="chalkboard">Virtual chalkboard</label>
|
||||
<textarea id="chalkboard" aria-labelledby="chalkboard-label"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+142
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user