feat: dynamic frontend configuration
This commit is contained in:
parent
9fda0a2c4b
commit
b0337d9f5b
@ -11,7 +11,11 @@ backend:
|
|||||||
frontend:
|
frontend:
|
||||||
# When an editable whiteboard is loading in a client,
|
# When an editable whiteboard is loading in a client,
|
||||||
# should it be started in read-only mode.
|
# should it be started in read-only mode.
|
||||||
setReadOnlyOnWhiteboardLoad: false
|
readOnlyOnWhiteboardLoad: false
|
||||||
# Show smallest screen indicator
|
# Show smallest screen indicator
|
||||||
showSmallestScreenIndicator: true
|
showSmallestScreenIndicator: true
|
||||||
# performance:
|
performance:
|
||||||
|
pointerEventsThreshold:
|
||||||
|
minDistDelta: 1
|
||||||
|
minTimeDelta: 33
|
||||||
|
refreshInfoFreq: 5
|
||||||
|
@ -30,13 +30,39 @@
|
|||||||
"frontend": {
|
"frontend": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["setReadOnlyOnWhiteboardLoad", "showSmallestScreenIndicator"],
|
"required": ["readOnlyOnWhiteboardLoad", "showSmallestScreenIndicator", "performance"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"setReadOnlyOnWhiteboardLoad": {
|
"readOnlyOnWhiteboardLoad": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"showSmallestScreenIndicator": {
|
"showSmallestScreenIndicator": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"performance": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["pointerEventsThreshold", "refreshInfoFreq"],
|
||||||
|
"properties": {
|
||||||
|
"pointerEventsThreshold": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["minDistDelta", "minTimeDelta"],
|
||||||
|
"properties": {
|
||||||
|
"minDistDelta": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"minTimeDelta": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refreshInfoFreq": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,19 +195,19 @@ function startBackendServer(port) {
|
|||||||
var whiteboardId = null;
|
var whiteboardId = null;
|
||||||
socket.on("disconnect", function () {
|
socket.on("disconnect", function () {
|
||||||
if (infoByWhiteboard.has(whiteboardId)) {
|
if (infoByWhiteboard.has(whiteboardId)) {
|
||||||
const whiteboardServerSideInfo = infoByWhiteboard.get(whiteboardId);
|
const whiteboardServerSideInfo = infoByWhiteboard.get(whiteboardId);
|
||||||
|
|
||||||
if (socket && socket.id) {
|
if (socket && socket.id) {
|
||||||
whiteboardServerSideInfo.deleteScreenResolutionOfClient(socket.id);
|
whiteboardServerSideInfo.deleteScreenResolutionOfClient(socket.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
whiteboardServerSideInfo.decrementNbConnectedUsers();
|
whiteboardServerSideInfo.decrementNbConnectedUsers();
|
||||||
|
|
||||||
if (whiteboardServerSideInfo.hasConnectedUser()) {
|
if (whiteboardServerSideInfo.hasConnectedUser()) {
|
||||||
socket.compress(false).broadcast.emit("refreshUserBadges", null); //Removes old user Badges
|
socket.compress(false).broadcast.emit("refreshUserBadges", null); //Removes old user Badges
|
||||||
} else {
|
} else {
|
||||||
infoByWhiteboard.delete(whiteboardId);
|
infoByWhiteboard.delete(whiteboardId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -224,6 +224,8 @@ function startBackendServer(port) {
|
|||||||
socket.on("joinWhiteboard", function (content) {
|
socket.on("joinWhiteboard", function (content) {
|
||||||
content = escapeAllContentStrings(content);
|
content = escapeAllContentStrings(content);
|
||||||
if (accessToken === "" || accessToken == content["at"]) {
|
if (accessToken === "" || accessToken == content["at"]) {
|
||||||
|
socket.emit("whiteboardConfig", { common: config.frontend });
|
||||||
|
|
||||||
whiteboardId = content["wid"];
|
whiteboardId = content["wid"];
|
||||||
socket.join(whiteboardId); //Joins room name=wid
|
socket.join(whiteboardId); //Joins room name=wid
|
||||||
if (!infoByWhiteboard.has(whiteboardId)) {
|
if (!infoByWhiteboard.has(whiteboardId)) {
|
||||||
|
@ -31,11 +31,11 @@ test("Complex object config override", () => {
|
|||||||
|
|
||||||
test("Override default config", () => {
|
test("Override default config", () => {
|
||||||
const defaultConfig = getDefaultConfig();
|
const defaultConfig = getDefaultConfig();
|
||||||
const overrideConfig1 = { frontend: { setReadOnlyOnWhiteboardLoad: true } };
|
const overrideConfig1 = { frontend: { readOnlyOnWhiteboardLoad: true } };
|
||||||
|
|
||||||
expect(
|
expect(deepMergeConfigs(defaultConfig, overrideConfig1).frontend.readOnlyOnWhiteboardLoad).toBe(
|
||||||
deepMergeConfigs(defaultConfig, overrideConfig1).frontend.setReadOnlyOnWhiteboardLoad
|
true
|
||||||
).toBe(true);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Dumb config is not valid", () => {
|
test("Dumb config is not valid", () => {
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
export const POINTER_EVENT_THRESHOLD_MIN_DIST_DELTA = 1; // 1px
|
|
||||||
export const POINTER_EVENT_THRESHOLD_MIN_TIME_DELTA = 10; // 1ms
|
|
||||||
export const REFRESH_INFO_FREQUENCY = 5; // 5 times per second
|
|
@ -8,47 +8,48 @@ import pdfjsLib from "pdfjs-dist/webpack";
|
|||||||
import shortcutFunctions from "./shortcutFunctions";
|
import shortcutFunctions from "./shortcutFunctions";
|
||||||
import ReadOnlyService from "./services/ReadOnlyService";
|
import ReadOnlyService from "./services/ReadOnlyService";
|
||||||
import InfoService from "./services/InfoService";
|
import InfoService from "./services/InfoService";
|
||||||
|
import { getQueryVariable, getSubDir } from "./utils";
|
||||||
|
import ConfigService from "./services/ConfigService";
|
||||||
|
|
||||||
|
let whiteboardId = getQueryVariable("whiteboardid");
|
||||||
|
const randomid = getQueryVariable("randomid");
|
||||||
|
if (randomid && !whiteboardId) {
|
||||||
|
//set random whiteboard on empty whiteboardid
|
||||||
|
whiteboardId = Array(2)
|
||||||
|
.fill(null)
|
||||||
|
.map(() => Math.random().toString(36).substr(2))
|
||||||
|
.join("");
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
urlParams.set("whiteboardid", whiteboardId);
|
||||||
|
window.location.search = urlParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
whiteboardId = whiteboardId || "myNewWhiteboard";
|
||||||
|
whiteboardId = unescape(encodeURIComponent(whiteboardId)).replace(/[^a-zA-Z0-9 ]/g, "");
|
||||||
|
const myUsername = getQueryVariable("username") || "unknown" + (Math.random() + "").substring(2, 6);
|
||||||
|
const accessToken = getQueryVariable("accesstoken") || "";
|
||||||
|
|
||||||
|
// Custom Html Title
|
||||||
|
const title = getQueryVariable("title");
|
||||||
|
if (!title === false) {
|
||||||
|
document.title = decodeURIComponent(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
const subdir = getSubDir();
|
||||||
|
let signaling_socket;
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
var whiteboardId = getQueryVariable("whiteboardid");
|
|
||||||
var randomid = getQueryVariable("randomid");
|
|
||||||
if (randomid && !whiteboardId) {
|
|
||||||
//set random whiteboard on empty whiteboardid
|
|
||||||
whiteboardId = Array(2)
|
|
||||||
.fill(null)
|
|
||||||
.map(() => Math.random().toString(36).substr(2))
|
|
||||||
.join("");
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
urlParams.set("whiteboardid", whiteboardId);
|
|
||||||
window.location.search = urlParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
whiteboardId = whiteboardId || "myNewWhiteboard";
|
|
||||||
whiteboardId = unescape(encodeURIComponent(whiteboardId)).replace(/[^a-zA-Z0-9 ]/g, "");
|
|
||||||
var myUsername = getQueryVariable("username");
|
|
||||||
var accessToken = getQueryVariable("accesstoken");
|
|
||||||
myUsername = myUsername || "unknown" + (Math.random() + "").substring(2, 6);
|
|
||||||
accessToken = accessToken || "";
|
|
||||||
var accessDenied = false;
|
|
||||||
|
|
||||||
// Custom Html Title
|
|
||||||
var title = getQueryVariable("title");
|
|
||||||
if (!title === false) {
|
|
||||||
document.title = decodeURIComponent(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = document.URL.substr(0, document.URL.lastIndexOf("/"));
|
|
||||||
var signaling_socket = null;
|
|
||||||
var urlSplit = url.split("/");
|
|
||||||
var subdir = "";
|
|
||||||
for (var i = 3; i < urlSplit.length; i++) {
|
|
||||||
subdir = subdir + "/" + urlSplit[i];
|
|
||||||
}
|
|
||||||
signaling_socket = io("", { path: subdir + "/ws-api" }); // Connect even if we are in a subdir behind a reverse proxy
|
signaling_socket = io("", { path: subdir + "/ws-api" }); // Connect even if we are in a subdir behind a reverse proxy
|
||||||
|
|
||||||
signaling_socket.on("connect", function () {
|
signaling_socket.on("connect", function () {
|
||||||
console.log("Websocket connected!");
|
console.log("Websocket connected!");
|
||||||
|
|
||||||
|
signaling_socket.on("whiteboardConfig", (serverResponse) => {
|
||||||
|
ConfigService.initFromServer(serverResponse);
|
||||||
|
// Inti whiteboard only when we have the config from the server
|
||||||
|
initWhiteboard();
|
||||||
|
});
|
||||||
|
|
||||||
signaling_socket.on("whiteboardInfoUpdate", (info) => {
|
signaling_socket.on("whiteboardInfoUpdate", (info) => {
|
||||||
InfoService.updateInfoFromServer(info);
|
InfoService.updateInfoFromServer(info);
|
||||||
whiteboard.updateSmallestScreenResolution();
|
whiteboard.updateSmallestScreenResolution();
|
||||||
@ -63,6 +64,7 @@ function main() {
|
|||||||
whiteboard.refreshUserBadges();
|
whiteboard.refreshUserBadges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let accessDenied = false;
|
||||||
signaling_socket.on("wrongAccessToken", function () {
|
signaling_socket.on("wrongAccessToken", function () {
|
||||||
if (!accessDenied) {
|
if (!accessDenied) {
|
||||||
accessDenied = true;
|
accessDenied = true;
|
||||||
@ -76,7 +78,9 @@ function main() {
|
|||||||
windowWidthHeight: { w: $(window).width(), h: $(window).height() },
|
windowWidthHeight: { w: $(window).width(), h: $(window).height() },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initWhiteboard() {
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
// by default set in readOnly mode
|
// by default set in readOnly mode
|
||||||
ReadOnlyService.activateReadOnlyMode();
|
ReadOnlyService.activateReadOnlyMode();
|
||||||
@ -599,7 +603,7 @@ function main() {
|
|||||||
// fix bug cursor not showing up
|
// fix bug cursor not showing up
|
||||||
whiteboard.refreshCursorAppearance();
|
whiteboard.refreshCursorAppearance();
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "production" && ConfigService.readOnlyOnWhiteboardLoad) {
|
||||||
ReadOnlyService.activateReadOnlyMode();
|
ReadOnlyService.activateReadOnlyMode();
|
||||||
InfoService.hideInfo();
|
InfoService.hideInfo();
|
||||||
} else {
|
} else {
|
||||||
@ -795,19 +799,6 @@ function main() {
|
|||||||
}, 1000 * options.hideAfter);
|
}, 1000 * options.hideAfter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get 'GET' parameter by variable name
|
|
||||||
function getQueryVariable(variable) {
|
|
||||||
var query = window.location.search.substring(1);
|
|
||||||
var vars = query.split("&");
|
|
||||||
for (var i = 0; i < vars.length; i++) {
|
|
||||||
var pair = vars[i].split("=");
|
|
||||||
if (pair[0] == variable) {
|
|
||||||
return pair[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default main;
|
export default main;
|
||||||
|
51
src/js/services/ConfigService.js
Normal file
51
src/js/services/ConfigService.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
class ConfigService {
|
||||||
|
/**
|
||||||
|
* @readonly
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
readOnlyOnWhiteboardLoad = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @readonly
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
showSmallestScreenIndicator = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @readonly
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
pointerEventsThresholdMinDistDelta = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @readonly
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
pointerEventsThresholdMinTimeDelta = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @readonly
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
refreshInfoInterval = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init the service from the config sent by the server
|
||||||
|
*
|
||||||
|
* @param {object} serverResponse
|
||||||
|
*/
|
||||||
|
initFromServer(serverResponse) {
|
||||||
|
const { common } = serverResponse;
|
||||||
|
const { readOnlyOnWhiteboardLoad, showSmallestScreenIndicator, performance } = common;
|
||||||
|
|
||||||
|
this.readOnlyOnWhiteboardLoad = readOnlyOnWhiteboardLoad;
|
||||||
|
this.showSmallestScreenIndicator = showSmallestScreenIndicator;
|
||||||
|
this.pointerEventsThresholdMinDistDelta = performance.pointerEventsThreshold.minDistDelta;
|
||||||
|
this.pointerEventsThresholdMinTimeDelta = performance.pointerEventsThreshold.minTimeDelta;
|
||||||
|
this.refreshInfoInterval = 1000 / performance.refreshInfoFreq;
|
||||||
|
|
||||||
|
console.log("Whiteboard config from server:", serverResponse, "parsed:", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ConfigService();
|
@ -1,6 +1,4 @@
|
|||||||
import { REFRESH_INFO_FREQUENCY } from "../const";
|
import ConfigService from "./ConfigService";
|
||||||
|
|
||||||
const REFRESH_INTERVAL = 1000 / REFRESH_INFO_FREQUENCY;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class the handle the information about the whiteboard
|
* Class the handle the information about the whiteboard
|
||||||
@ -90,7 +88,7 @@ class InfoService {
|
|||||||
// refresh only on a specific interval to reduce
|
// refresh only on a specific interval to reduce
|
||||||
// refreshing cost
|
// refreshing cost
|
||||||
this.refreshDisplayedInfo();
|
this.refreshDisplayedInfo();
|
||||||
}, REFRESH_INTERVAL);
|
}, ConfigService.refreshInfoInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
hideInfo() {
|
hideInfo() {
|
||||||
|
@ -14,3 +14,31 @@ export function computeDist(p1, p2) {
|
|||||||
export function getCurrentTimeMs() {
|
export function getCurrentTimeMs() {
|
||||||
return new Date().getTime();
|
return new Date().getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get 'GET' parameter by variable name
|
||||||
|
* @param variable
|
||||||
|
* @return {boolean|*}
|
||||||
|
*/
|
||||||
|
export function getQueryVariable(variable) {
|
||||||
|
const query = window.location.search.substring(1);
|
||||||
|
const vars = query.split("&");
|
||||||
|
for (let i = 0; i < vars.length; i++) {
|
||||||
|
const pair = vars[i].split("=");
|
||||||
|
if (pair[0] === variable) {
|
||||||
|
return pair[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSubDir() {
|
||||||
|
const url = document.URL.substr(0, document.URL.lastIndexOf("/"));
|
||||||
|
const urlSplit = url.split("/");
|
||||||
|
let subdir = "";
|
||||||
|
for (let i = 3; i < urlSplit.length; i++) {
|
||||||
|
subdir = subdir + "/" + urlSplit[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return subdir;
|
||||||
|
}
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import { dom } from "@fortawesome/fontawesome-svg-core";
|
import { dom } from "@fortawesome/fontawesome-svg-core";
|
||||||
import { getCurrentTimeMs } from "./utils";
|
import { getCurrentTimeMs } from "./utils";
|
||||||
import Point from "./classes/Point";
|
import Point from "./classes/Point";
|
||||||
import {
|
|
||||||
POINTER_EVENT_THRESHOLD_MIN_DIST_DELTA,
|
|
||||||
POINTER_EVENT_THRESHOLD_MIN_TIME_DELTA,
|
|
||||||
} from "./const";
|
|
||||||
import ReadOnlyService from "./services/ReadOnlyService";
|
import ReadOnlyService from "./services/ReadOnlyService";
|
||||||
import InfoService from "./services/InfoService";
|
import InfoService from "./services/InfoService";
|
||||||
|
import ConfigService from "./services/ConfigService";
|
||||||
|
|
||||||
const RAD_TO_DEG = 180.0 / Math.PI;
|
const RAD_TO_DEG = 180.0 / Math.PI;
|
||||||
const DEG_TO_RAD = Math.PI / 180.0;
|
const DEG_TO_RAD = Math.PI / 180.0;
|
||||||
@ -215,11 +212,11 @@ const whiteboard = {
|
|||||||
const pointerSentTime = getCurrentTimeMs();
|
const pointerSentTime = getCurrentTimeMs();
|
||||||
if (
|
if (
|
||||||
pointerSentTime - _this.lastPointerSentTime >
|
pointerSentTime - _this.lastPointerSentTime >
|
||||||
POINTER_EVENT_THRESHOLD_MIN_TIME_DELTA
|
ConfigService.pointerEventsThresholdMinTimeDelta
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
_this.lastPointerPosition.distTo(currentPos) >
|
_this.lastPointerPosition.distTo(currentPos) >
|
||||||
POINTER_EVENT_THRESHOLD_MIN_DIST_DELTA
|
ConfigService.pointerEventsThresholdMinDistDelta
|
||||||
) {
|
) {
|
||||||
_this.lastPointerSentTime = pointerSentTime;
|
_this.lastPointerSentTime = pointerSentTime;
|
||||||
_this.lastPointerPosition = currentPos;
|
_this.lastPointerPosition = currentPos;
|
||||||
@ -545,11 +542,14 @@ const whiteboard = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const pointerSentTime = getCurrentTimeMs();
|
const pointerSentTime = getCurrentTimeMs();
|
||||||
if (pointerSentTime - _this.lastPointerSentTime > POINTER_EVENT_THRESHOLD_MIN_TIME_DELTA) {
|
if (
|
||||||
|
pointerSentTime - _this.lastPointerSentTime >
|
||||||
|
ConfigService.pointerEventsThresholdMinTimeDelta
|
||||||
|
) {
|
||||||
const newPointerPosition = currentPos;
|
const newPointerPosition = currentPos;
|
||||||
if (
|
if (
|
||||||
_this.lastPointerPosition.distTo(newPointerPosition) >
|
_this.lastPointerPosition.distTo(newPointerPosition) >
|
||||||
POINTER_EVENT_THRESHOLD_MIN_DIST_DELTA
|
ConfigService.pointerEventsThresholdMinDistDelta
|
||||||
) {
|
) {
|
||||||
_this.lastPointerSentTime = pointerSentTime;
|
_this.lastPointerSentTime = pointerSentTime;
|
||||||
_this.lastPointerPosition = newPointerPosition;
|
_this.lastPointerPosition = newPointerPosition;
|
||||||
@ -882,12 +882,12 @@ const whiteboard = {
|
|||||||
// At least 100 ms between messages to reduce server load
|
// At least 100 ms between messages to reduce server load
|
||||||
if (
|
if (
|
||||||
pointerSentTime - _this.lastPointerSentTime >
|
pointerSentTime - _this.lastPointerSentTime >
|
||||||
POINTER_EVENT_THRESHOLD_MIN_TIME_DELTA
|
ConfigService.pointerEventsThresholdMinTimeDelta
|
||||||
) {
|
) {
|
||||||
// Minimal distance between messages to reduce server load
|
// Minimal distance between messages to reduce server load
|
||||||
if (
|
if (
|
||||||
_this.lastPointerPosition.distTo(newPointerPosition) >
|
_this.lastPointerPosition.distTo(newPointerPosition) >
|
||||||
POINTER_EVENT_THRESHOLD_MIN_DIST_DELTA
|
ConfigService.pointerEventsThresholdMinDistDelta
|
||||||
) {
|
) {
|
||||||
_this.lastPointerSentTime = pointerSentTime;
|
_this.lastPointerSentTime = pointerSentTime;
|
||||||
_this.lastPointerPosition = newPointerPosition;
|
_this.lastPointerPosition = newPointerPosition;
|
||||||
|
Loading…
Reference in New Issue
Block a user