Merge branch 'master' into rotation

# Conflicts:
#	package.json
This commit is contained in:
raphael
2020-05-22 17:35:22 +02:00
43 changed files with 7692 additions and 1282 deletions

View File

@@ -1,3 +1,7 @@
:root {
--selected-icon-bg-color: #dfdfdf;
}
body {
position: relative;
margin: 0px;
@@ -13,7 +17,8 @@ body {
.btn-group button {
background: transparent;
border: 1px solid #636060;
border: 2px solid #636060;
margin: -1px;
/* Green border */
color: black;
/* White text */
@@ -25,15 +30,29 @@ body {
/* Float the buttons side by side */
font-size: 1.2em;
height: 45px;
width: 50px;
}
button::-moz-focus-inner {
border: 0;
}
.btn-group button:not(:last-child) {
border-right: none;
/* Prevent double borders */
.whiteboard-edit-group.group-disabled {
background: repeating-linear-gradient(
45deg,
rgba(255, 166, 0, 0.366),
rgba(255, 166, 0, 0.366) 10px,
rgba(255, 166, 0, 0.666) 10px,
rgba(255, 166, 0, 0.666) 20px
);
}
/*
* Deactivate all pointer events on all the children
* of a group when it's disabled.
*/
.whiteboard-edit-group.group-disabled > * {
pointer-events: none;
}
/* Clear floats (clearfix hack) */
@@ -57,12 +76,13 @@ button {
.btn-group {
background-color: #808080ab;
margin-left: 5px;
margin-bottom: 5px;
float: left;
position: relative;
}
.whiteboardTool.active {
background: #bfbfbf;
.whiteboard-tool.active:not(:disabled) {
background: var(--selected-icon-bg-color);
}
#whiteboardThicknessSlider {
@@ -73,19 +93,21 @@ button {
background: transparent;
outline: none;
opacity: 1;
-webkit-transition: opacity .15s ease-in-out;
transition: opacity .15s ease-in-out;
-webkit-transition: opacity 0.15s ease-in-out;
transition: opacity 0.15s ease-in-out;
}
.textBox.active {
border: 1px dashed gray;
}
.textBox>.removeIcon, .textBox>.moveIcon {
.textBox > .removeIcon,
.textBox > .moveIcon {
display: none;
}
.textBox.active>.removeIcon, .textBox.active>.moveIcon {
.textBox.active > .removeIcon,
.textBox.active > .moveIcon {
display: block;
}
@@ -95,4 +117,18 @@ button {
border: 0px;
min-width: 50px;
cursor: pointer;
}
}
#displayWhiteboardInfoBtn.active {
background: var(--selected-icon-bg-color);
}
#whiteboardInfoContainer {
position: absolute;
bottom: 10px;
right: 10px;
}
.displayNone {
display: none;
}

View File

@@ -1,129 +1,262 @@
<!DOCTYPE html>
<html>
<head>
<title>Whiteboard</title>
<meta charset="utf-8" />
<link rel="icon" type="image/vnd.microsoft.icon" href="favicon.ico" />
</head>
<head>
<title>Whiteboard</title>
<meta charset="utf-8" />
<link rel="icon" type="image/vnd.microsoft.icon" href="favicon.ico">
</head>
<body>
<!---Whiteboard container -!-->
<div id="whiteboardContainer"></div>
<body>
<!---Whiteboard container -!-->
<div id="whiteboardContainer"></div>
<!---Toolbar -!-->
<div id="toolbar" style="position: absolute; top: 10px; left: 10px;">
<div class="btn-group">
<button
id="whiteboardLockBtn"
style="background-color: orange;"
title="View and Write"
type="button"
>
<i class="fa fa-lock"></i>
</button>
<button id="whiteboardUnlockBtn" title="View Only" type="button">
<i class="fa fa-lock-open"></i>
</button>
</div>
<!---Toolbar -!-->
<div id="toolbar" style="position: absolute; top: 10px; left: 10px;">
<div class="btn-group">
<button id="whiteboardTrashBtn" title="Clear the whiteboard" type="button" class="whiteboardBtn">
<i class="fa fa-trash"></i>
</button>
<button style="position:absolute; left:0px; top:0px; width: 46px; display:none;"
id="whiteboardTrashBtnConfirm" title="Confirm clear..." type="button" class="whiteboardBtn">
<i class="fa fa-check"></i>
</button>
<button id="whiteboardUndoBtn" title="Undo your last step" type="button" class="whiteboardBtn">
<i class="fa fa-undo"></i>
</button>
<button id="whiteboardRedoBtn" title="Redo your last undo" type="button" class="whiteboardBtn">
<i class="fa fa-redo"></i>
</button>
</div>
<div class="btn-group whiteboard-edit-group">
<button id="whiteboardTrashBtn" title="Clear the whiteboard" type="button">
<i class="fa fa-trash"></i>
</button>
<button
style="position: absolute; left: 0px; top: 0px; width: 46px; display: none;"
id="whiteboardTrashBtnConfirm"
title="Confirm clear..."
type="button"
>
<i class="fa fa-check"></i>
</button>
<button id="whiteboardUndoBtn" title="Undo your last step" type="button">
<i class="fa fa-undo"></i>
</button>
<button id="whiteboardRedoBtn" title="Redo your last undo" type="button">
<i class="fa fa-redo"></i>
</button>
</div>
<div class="btn-group">
<button tool="mouse" title="Take the mouse" type="button" class="whiteboardTool">
<i class="fa fa-mouse-pointer"></i>
</button>
<button style="padding-bottom: 11px;" tool="recSelect" title="Select an area" type="button"
class="whiteboardTool">
<img src="./images/dottedRec.png">
</button>
<button tool="pen" title="Take the pen" type="button" class="whiteboardTool active">
<i class="fa fa-pencil-alt"></i>
</button>
<button style="padding-bottom: 8px; padding-top: 6px;" tool="line" title="draw a line" type="button"
class="whiteboardTool">
</button>
<button tool="rect" title="draw a rectangle" type="button" class="whiteboardTool">
<i class="far fa-square"></i>
</button>
<button tool="circle" title="draw a circle" type="button" class="whiteboardTool">
<i class="far fa-circle"></i>
</button>
<button tool="text" title="write text" type="button" class="whiteboardTool">
<i class="fas fa-font"></i>
</button>
<button tool="eraser" title="take the eraser" type="button" class="whiteboardTool">
<i class="fa fa-eraser"></i>
</button>
</div>
<div class="btn-group whiteboard-edit-group">
<button tool="mouse" title="Take the mouse" type="button" class="whiteboard-tool">
<i class="fa fa-mouse-pointer"></i>
</button>
<button
style="padding-bottom: 11px;"
tool="recSelect"
title="Select an area"
type="button"
class="whiteboard-tool"
>
<img src="./images/dottedRec.png" />
</button>
<button
tool="pen"
title="Take the pen"
type="button"
class="whiteboard-tool active"
>
<i class="fa fa-pencil-alt"></i>
</button>
<button
style="padding-bottom: 8px; padding-top: 6px;"
tool="line"
title="draw a line"
type="button"
class="whiteboard-tool"
>
</button>
<button tool="rect" title="draw a rectangle" type="button" class="whiteboard-tool">
<i class="far fa-square"></i>
</button>
<button tool="circle" title="draw a circle" type="button" class="whiteboard-tool">
<i class="far fa-circle"></i>
</button>
<button tool="text" title="write text" type="button" class="whiteboard-tool">
<i class="fas fa-font"></i>
</button>
<button tool="eraser" title="take the eraser" type="button" class="whiteboard-tool">
<i class="fa fa-eraser"></i>
</button>
</div>
<div class="btn-group">
<button style="width: 190px; cursor:default;">
<div class="activeToolIcon" style="position:absolute; top:2px; left:2px; font-size: 0.6em;"><i
class="fa fa-pencil-alt"></i></div>
<img style="position: absolute; left: 11px; top: 16px; height:14px; width:130px;"
src="./images/slider-background.svg">
<input title="Thickness" id="whiteboardThicknessSlider"
style="position: absolute; left:9px; width: 130px; top: 15px;" type="range" min="1" max="50"
value="3">
<div id="whiteboardColorpicker"
style="position: absolute; left: 155px; top: 10px; width: 26px; height: 23px; border-radius: 3px; border: 1px solid darkgrey;"
data-color="#000000">
</div>
</button>
</div>
<div class="btn-group whiteboard-edit-group">
<button style="width: 190px; cursor: default;">
<div
class="activeToolIcon"
style="position: absolute; top: 2px; left: 2px; font-size: 0.6em;"
>
<i class="fa fa-pencil-alt"></i>
</div>
<img
style="
position: absolute;
left: 11px;
top: 16px;
height: 14px;
width: 130px;
"
src="./images/slider-background.svg"
/>
<input
title="Thickness"
id="whiteboardThicknessSlider"
style="position: absolute; left: 9px; width: 130px; top: 15px;"
type="range"
min="1"
max="50"
value="3"
/>
<div
id="whiteboardColorpicker"
style="
position: absolute;
left: 155px;
top: 10px;
width: 26px;
height: 23px;
border-radius: 3px;
border: 1px solid darkgrey;
"
data-color="#000000"
></div>
</button>
</div>
<div class="btn-group">
<button id="saveAsImageBtn" title="Save whiteboard as image" type="button" class="whiteboardBtn">
<i class="fas fa-image"></i>
<i style="position: absolute; top: 3px; left: 2px; color: #000000; font-size: 0.5em; "
class="fas fa-save"></i>
</button>
<button style="position: relative; display: none;" id="uploadWebDavBtn" title="Save whiteboard to webdav"
type="button" class="whiteboardBtn">
<div class="btn-group whiteboard-edit-group">
<button id="addImgToCanvasBtn" title="Upload Image to whiteboard" type="button">
<i class="fas fa-image"></i>
<i
style="
position: absolute;
top: 3px;
left: 2px;
color: #000000;
font-size: 0.5em;
"
class="fas fa-upload"
></i>
</button>
<i class="fas fa-globe"></i>
<i style="position: absolute; top: 3px; left: 2px; color: #000000; font-size: 0.5em; "
class="fas fa-save"></i>
</button>
<button style="position: relative;" id="saveAsJSONBtn" title="Save whiteboard as JSON" type="button"
class="whiteboardBtn">
<i class="far fa-file-alt"></i>
<i style="position: absolute; top: 3px; left: 2px; color: #000000; font-size: 0.5em; "
class="fas fa-save"></i>
</button>
</div>
<button
style="position: relative;"
id="uploadJsonBtn"
title="Load saved JSON to whiteboard"
type="button"
>
<i class="far fa-file-alt"></i>
<i
style="
position: absolute;
top: 3px;
left: 2px;
color: #000000;
font-size: 0.5em;
"
class="fas fa-upload"
></i>
</button>
<div class="btn-group">
<button id="addImgToCanvasBtn" title="Upload Image to whiteboard" type="button" class="whiteboardBtn">
<i class="fas fa-image"></i>
<i style="position: absolute; top: 3px; left: 2px; color: #000000; font-size: 0.5em; "
class="fas fa-upload"></i>
</button>
<input style="display: none;" id="myFile" type="file" />
</div>
<button style="position: relative;" id="uploadJsonBtn" title="Load saved JSON to whiteboard" type="button"
class="whiteboardBtn">
<div class="btn-group">
<button id="saveAsImageBtn" title="Save whiteboard as image" type="button">
<i class="fas fa-image"></i>
<i
style="
position: absolute;
top: 3px;
left: 2px;
color: #000000;
font-size: 0.5em;
"
class="fas fa-save"
></i>
</button>
<button
style="position: relative; display: none;"
id="uploadWebDavBtn"
title="Save whiteboard to webdav"
type="button"
>
<i class="fas fa-globe"></i>
<i
style="
position: absolute;
top: 3px;
left: 2px;
color: #000000;
font-size: 0.5em;
"
class="fas fa-save"
></i>
</button>
<button
style="position: relative;"
id="saveAsJSONBtn"
title="Save whiteboard as JSON"
type="button"
>
<i class="far fa-file-alt"></i>
<i
style="
position: absolute;
top: 3px;
left: 2px;
color: #000000;
font-size: 0.5em;
"
class="fas fa-save"
></i>
</button>
<i class="far fa-file-alt"></i>
<i style="position: absolute; top: 3px; left: 2px; color: #000000; font-size: 0.5em; "
class="fas fa-upload"></i>
</button>
<input style="display:none;" id="myFile" type="file" />
<button id="shareWhiteboardBtn" title="share whiteboard" type="button">
<i class="fas fa-share-square"></i>
</button>
<button id="shareWhiteboardBtn" title="share whiteboard" type="button">
<i class="fas fa-share-square"></i>
</button>
</div>
<button id="displayWhiteboardInfoBtn" title="Show whiteboard info" type="button">
<i class="fas fa-info-circle"></i>
</button>
</div>
<div class="btn-group minGroup">
<button style="width: 25px; padding: 11px 11px;" id="minMaxBtn" title="hide buttons" type="button">
<i id="minBtn" style="position:relative; left:-5px;" class="fas fa-angle-left"></i>
<i id="maxBtn" style="position:relative; left:-5px; display: none;" class="fas fa-angle-right"></i>
</button>
</div>
</div>
<div class="btn-group minGroup">
<button
style="width: 25px; padding: 11px 11px;"
id="minMaxBtn"
title="hide buttons"
type="button"
>
<i
id="minBtn"
style="position: relative; left: -5px;"
class="fas fa-angle-left"
></i>
<i
id="maxBtn"
style="position: relative; left: -5px; display: none;"
class="fas fa-angle-right"
></i>
</button>
</div>
</div>
</body>
</html>
<div id="whiteboardInfoContainer">
<p><b>Whiteboard information:</b></p>
<p># connected users: <i id="connectedUsersCount">0</i></p>
<p>Smallest screen resolution: <i id="smallestScreenResolution">Unknown.</i></p>
<p># msg. sent to server: <i id="messageSentCount">0</i></p>
<p># msg. received from server: <i id="messageReceivedCount">0</i></p>
</div>
</body>
</html>

79
src/js/classes/Point.js Normal file
View File

@@ -0,0 +1,79 @@
import { computeDist } from "../utils";
class Point {
/**
* @type {number}
*/
#x;
get x() {
return this.#x;
}
/**
* @type {number}
*/
#y;
get y() {
return this.#y;
}
/**
* @type {Point}
*/
static #lastKnownPos = new Point(0, 0);
static get lastKnownPos() {
return Point.#lastKnownPos;
}
/**
* @param {number} x
* @param {number} y
*/
constructor(x, y) {
this.#x = x;
this.#y = y;
}
get isZeroZero() {
return this.#x === 0 && this.#y === 0;
}
/**
* Get a Point object from an event
* @param {event} e
* @returns {Point}
*/
static fromEvent(e) {
// the epsilon hack is required to detect touches
const epsilon = 0.0001;
let x = (e.offsetX || e.pageX - $(e.target).offset().left) + epsilon;
let y = (e.offsetY || e.pageY - $(e.target).offset().top) + epsilon;
if (Number.isNaN(x) || Number.isNaN(y) || (x === epsilon && y === epsilon)) {
// if it's a touch actually
if (e.touches && e.touches.length && e.touches.length > 0) {
const touch = e.touches[0];
x = touch.clientX - $("#mouseOverlay").offset().left;
y = touch.clientY - $("#mouseOverlay").offset().top;
} else {
// if it's a touchend event
return Point.#lastKnownPos;
}
}
Point.#lastKnownPos = new Point(x - epsilon, y - epsilon);
return Point.#lastKnownPos;
}
/**
* Compute euclidean distance between points
*
* @param {Point} otherPoint
* @returns {number}
*/
distTo(otherPoint) {
return computeDist(this, otherPoint);
}
}
export default Point;

View File

@@ -16,6 +16,9 @@ import {
faAngleRight,
faSortDown,
faExpandArrowsAlt,
faLock,
faLockOpen,
faInfoCircle,
} from "@fortawesome/free-solid-svg-icons";
import {
faSquare,
@@ -46,7 +49,10 @@ library.add(
faCircle,
faFile,
faFileAlt,
faPlusSquare
faPlusSquare,
faLock,
faLockOpen,
faInfoCircle
);
dom.i2svg()
dom.i2svg();

View File

@@ -1,7 +1,7 @@
import "jquery-ui/ui/core";
import "jquery-ui/ui/widgets/draggable";
import "jquery-ui/ui/widgets/resizable";
import "jquery-ui-rotatable/jquery.ui.rotatable"
import "jquery-ui-rotatable/jquery.ui.rotatable";
import "jquery-ui/themes/base/resizable.css";
import "../css/main.css";
@@ -9,19 +9,19 @@ import "./icons";
import main from "./main";
$(document).ready(function () {
// Set correct width height on mobile browsers
const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
if (isChrome) {
$('head').append('<meta name="viewport" content="width=device-width, initial-scale=0.52, maximum-scale=1" />');
$("head").append(
'<meta name="viewport" content="width=device-width, initial-scale=0.52, maximum-scale=1" />'
);
} else {
$('head').append('<meta name="viewport" content="width=1400" />');
$("head").append('<meta name="viewport" content="width=1400" />');
}
main();
});
if (module.hot) {
module.hot.accept();
}

View File

@@ -1,48 +1,48 @@
/* -----------
KEYBINDINGS
----------- */
----------- */
//> defmod is "command" on OS X and "ctrl" elsewhere
//Advanced Example: 'defmod-k j' -> For this to fire you have to first press both ctrl and k, and then j.
//Advanced Example: 'defmod-k j' -> For this to fire you have to first press both ctrl and k, and then j.
const keybinds = {
// 'key(s)' : 'function',
'defmod-shift-z' : 'clearWhiteboard',
'defmod-z' : 'undoStep',
'defmod-y' : 'redoStep',
'defmod-x' : 'setTool_recSelect',
'defmod-m' : 'setTool_mouse',
'defmod-p' : 'setTool_pen',
'defmod-l' : 'setTool_line',
'defmod-r' : 'setTool_rect',
'defmod-c' : 'setTool_circle',
'defmod-shift-f' : 'toggleLineRecCircle',
'defmod-shift-x' : 'togglePenEraser',
'defmod-shift-r' : 'toggleMainColors',
'defmod-a' : 'setTool_text',
'defmod-e' : 'setTool_eraser',
'defmod-up' : 'thickness_bigger',
'defmod-down' : 'thickness_smaller',
'defmod-shift-c' : 'openColorPicker',
'defmod-shift-1' : 'setDrawColorBlack',
'defmod-shift-2' : 'setDrawColorBlue',
'defmod-shift-3' : 'setDrawColorGreen',
'defmod-shift-4' : 'setDrawColorYellow',
'defmod-shift-5' : 'setDrawColorRed',
'defmod-s' : 'saveWhiteboardAsImage',
'defmod-shift-k' : 'saveWhiteboardAsJson',
'defmod-shift-i' : 'uploadWhiteboardToWebDav',
'defmod-shift-j' : 'uploadJsonToWhiteboard',
'defmod-shift-s' : 'shareWhiteboard',
'tab' : 'hideShowControls',
'up' : 'moveDraggableUp',
'down' : 'moveDraggableDown',
'left' : 'moveDraggableLeft',
'right' : 'moveDraggableRight',
'defmod-enter' : 'dropDraggable',
'shift-enter' : 'addToBackground',
'escape' : 'cancelAllActions',
'del' : 'deleteSelection'
}
"defmod-shift-z": "clearWhiteboard",
"defmod-z": "undoStep",
"defmod-y": "redoStep",
"defmod-x": "setTool_recSelect",
"defmod-m": "setTool_mouse",
"defmod-p": "setTool_pen",
"defmod-l": "setTool_line",
"defmod-r": "setTool_rect",
"defmod-c": "setTool_circle",
"defmod-shift-f": "toggleLineRecCircle",
"defmod-shift-x": "togglePenEraser",
"defmod-shift-r": "toggleMainColors",
"defmod-a": "setTool_text",
"defmod-e": "setTool_eraser",
"defmod-up": "thickness_bigger",
"defmod-down": "thickness_smaller",
"defmod-shift-c": "openColorPicker",
"defmod-shift-1": "setDrawColorBlack",
"defmod-shift-2": "setDrawColorBlue",
"defmod-shift-3": "setDrawColorGreen",
"defmod-shift-4": "setDrawColorYellow",
"defmod-shift-5": "setDrawColorRed",
"defmod-s": "saveWhiteboardAsImage",
"defmod-shift-k": "saveWhiteboardAsJson",
"defmod-shift-i": "uploadWhiteboardToWebDav",
"defmod-shift-j": "uploadJsonToWhiteboard",
"defmod-shift-s": "shareWhiteboard",
tab: "hideShowControls",
up: "moveDraggableUp",
down: "moveDraggableDown",
left: "moveDraggableLeft",
right: "moveDraggableRight",
"defmod-enter": "dropDraggable",
"shift-enter": "addToBackground",
escape: "cancelAllActions",
del: "deleteSelection",
};
export default keybinds;
export default keybinds;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,118 @@
import { getThrottling } from "./ConfigService.utils";
/**
* Class to hold the configuration sent by the backend
*/
class ConfigService {
/**
* @type {object}
*/
#configFromServer = {};
get configFromServer() {
return this.#configFromServer;
}
/**
* @type {{displayInfo: boolean, setReadOnly: boolean}}
* @readonly
*/
#onWhiteboardLoad = { setReadOnly: false, displayInfo: false };
get readOnlyOnWhiteboardLoad() {
return this.#onWhiteboardLoad.setReadOnly;
}
get displayInfoOnWhiteboardLoad() {
return this.#onWhiteboardLoad.displayInfo;
}
/**
* @type {boolean}
*/
#showSmallestScreenIndicator = true;
get showSmallestScreenIndicator() {
return this.#showSmallestScreenIndicator;
}
/**
* @type {string}
*/
#imageDownloadFormat = "png";
get imageDownloadFormat() {
return this.#imageDownloadFormat;
}
/**
* @type {boolean}
*/
#drawBackgroundGrid = false;
get drawBackgroundGrid() {
return this.#drawBackgroundGrid;
}
/**
* @type {string}
*/
#backgroundGridImage = "bg_grid.png";
get backgroundGridImage() {
return this.#backgroundGridImage;
}
/**
* @type {{minDistDelta: number, minTimeDelta: number}}
*/
#pointerEventsThrottling = { minDistDelta: 0, minTimeDelta: 0 };
get pointerEventsThrottling() {
return this.#pointerEventsThrottling;
}
/**
* @type {number}
*/
#refreshInfoInterval = 1000;
get refreshInfoInterval() {
return this.#refreshInfoInterval;
}
/**
* Init the service from the config sent by the server
*
* @param {object} configFromServer
*/
initFromServer(configFromServer) {
this.#configFromServer = configFromServer;
const { common } = configFromServer;
const {
onWhiteboardLoad,
showSmallestScreenIndicator,
imageDownloadFormat,
drawBackgroundGrid,
backgroundGridImage,
performance,
} = common;
this.#onWhiteboardLoad = onWhiteboardLoad;
this.#showSmallestScreenIndicator = showSmallestScreenIndicator;
this.#imageDownloadFormat = imageDownloadFormat;
this.#drawBackgroundGrid = drawBackgroundGrid;
this.#backgroundGridImage = backgroundGridImage;
this.#refreshInfoInterval = 1000 / performance.refreshInfoFreq;
console.log("Whiteboard config from server:", configFromServer, "parsed:", this);
}
/**
* Refresh config that depends on the number of user connected to whiteboard
*
* @param {number} userCount
*/
refreshUserCountDependant(userCount) {
const { configFromServer } = this;
const { common } = configFromServer;
const { performance } = common;
const { pointerEventsThrottling } = performance;
this.#pointerEventsThrottling = getThrottling(pointerEventsThrottling, userCount);
}
}
export default new ConfigService();

View File

@@ -0,0 +1,21 @@
/**
* Helper to extract the correct throttling values based on the config and the number of user
*
* @param {Array.<{fromUserCount: number, minDistDelta: number, maxFreq: number}>} pointerEventsThrottling
* @param {number} userCount
* @return {{minDistDelta: number, minTimeDelta: number}}
*/
export function getThrottling(pointerEventsThrottling, userCount) {
let tmpOut = pointerEventsThrottling[0];
let lastDistToUserCount = userCount - tmpOut.fromUserCount;
if (lastDistToUserCount < 0) lastDistToUserCount = Number.MAX_VALUE;
for (const el of pointerEventsThrottling) {
const distToUserCount = userCount - el.fromUserCount;
if (el.fromUserCount <= userCount && distToUserCount <= lastDistToUserCount) {
tmpOut = el;
lastDistToUserCount = distToUserCount;
}
}
return { minDistDelta: tmpOut.minDistDelta, minTimeDelta: 1000 * (1 / tmpOut.maxFreq) };
}

View File

@@ -0,0 +1,29 @@
import { getThrottling } from "./ConfigService.utils";
test("Simple throttling config", () => {
const throttling = [{ fromUserCount: 0, minDistDelta: 1, maxFreq: 1 }];
const target0 = { minDistDelta: 1, minTimeDelta: 1000 };
expect(getThrottling(throttling, 0)).toEqual(target0);
const target100 = { minDistDelta: 1, minTimeDelta: 1000 };
expect(getThrottling(throttling, 100)).toEqual(target100);
});
test("Complex throttling config", () => {
// mix ordering
const throttling = [
{ fromUserCount: 100, minDistDelta: 100, maxFreq: 1 },
{ fromUserCount: 0, minDistDelta: 1, maxFreq: 1 },
{ fromUserCount: 50, minDistDelta: 50, maxFreq: 1 },
];
const target0 = { minDistDelta: 1, minTimeDelta: 1000 };
expect(getThrottling(throttling, 0)).toEqual(target0);
const target50 = { minDistDelta: 50, minTimeDelta: 1000 };
expect(getThrottling(throttling, 50)).toEqual(target50);
const target100 = { minDistDelta: 100, minTimeDelta: 1000 };
expect(getThrottling(throttling, 100)).toEqual(target100);
});

View File

@@ -0,0 +1,137 @@
import ConfigService from "./ConfigService";
/**
* Class the handle the information about the whiteboard
*/
class InfoService {
/**
* @type {boolean}
*/
#infoAreDisplayed = false;
get infoAreDisplayed() {
return this.#infoAreDisplayed;
}
/**
* Holds the number of user connected to the server
*
* @type {number}
*/
#nbConnectedUsers = -1;
get nbConnectedUsers() {
return this.#nbConnectedUsers;
}
/**
* @type {{w: number, h: number}}
*/
#smallestScreenResolution = undefined;
get smallestScreenResolution() {
return this.#smallestScreenResolution;
}
/**
* @type {number}
*/
#nbMessagesSent = 0;
get nbMessagesSent() {
return this.#nbMessagesSent;
}
/**
* @type {number}
*/
#nbMessagesReceived = 0;
get nbMessagesReceived() {
return this.#nbMessagesReceived;
}
/**
* Holds the interval Id
* @type {number}
*/
#refreshInfoIntervalId = undefined;
get refreshInfoIntervalId() {
return this.#refreshInfoIntervalId;
}
/**
* @param {number} nbConnectedUsers
* @param {{w: number, h: number}} smallestScreenResolution
*/
updateInfoFromServer({ nbConnectedUsers, smallestScreenResolution = undefined }) {
if (this.#nbConnectedUsers !== nbConnectedUsers) {
// Refresh config service parameters on nb connected user change
ConfigService.refreshUserCountDependant(nbConnectedUsers);
}
this.#nbConnectedUsers = nbConnectedUsers;
if (smallestScreenResolution) {
this.#smallestScreenResolution = smallestScreenResolution;
}
}
incrementNbMessagesReceived() {
this.#nbMessagesReceived++;
}
incrementNbMessagesSent() {
this.#nbMessagesSent++;
}
refreshDisplayedInfo() {
const {
nbMessagesReceived,
nbMessagesSent,
nbConnectedUsers,
smallestScreenResolution: ssr,
} = this;
$("#messageReceivedCount")[0].innerText = String(nbMessagesReceived);
$("#messageSentCount")[0].innerText = String(nbMessagesSent);
$("#connectedUsersCount")[0].innerText = String(nbConnectedUsers);
$("#smallestScreenResolution")[0].innerText = ssr ? `(${ssr.w}, ${ssr.h})` : "Unknown";
}
/**
* Show the info div
*/
displayInfo() {
$("#whiteboardInfoContainer").toggleClass("displayNone", false);
$("#displayWhiteboardInfoBtn").toggleClass("active", true);
this.#infoAreDisplayed = true;
this.refreshDisplayedInfo();
this.#refreshInfoIntervalId = setInterval(() => {
// refresh only on a specific interval to reduce
// refreshing cost
this.refreshDisplayedInfo();
}, ConfigService.refreshInfoInterval);
}
/**
* Hide the info div
*/
hideInfo() {
$("#whiteboardInfoContainer").toggleClass("displayNone", true);
$("#displayWhiteboardInfoBtn").toggleClass("active", false);
this.#infoAreDisplayed = false;
const { refreshInfoIntervalId } = this;
if (refreshInfoIntervalId) {
clearInterval(refreshInfoIntervalId);
this.#refreshInfoIntervalId = undefined;
}
}
/**
* Switch between hiding and showing the info div
*/
toggleDisplayInfo() {
const { infoAreDisplayed } = this;
if (infoAreDisplayed) {
this.hideInfo();
} else {
this.displayInfo();
}
}
}
export default new InfoService();

View File

@@ -0,0 +1,57 @@
/**
* Class the handle the read-only logic
*/
class ReadOnlyService {
/**
* @type {boolean}
*/
#readOnlyActive = true;
get readOnlyActive() {
return this.#readOnlyActive;
}
/**
* @type {object}
*/
#previousToolHtmlElem = null;
get previousToolHtmlElem() {
return this.#previousToolHtmlElem;
}
/**
* Activate read-only mode
*/
activateReadOnlyMode() {
this.#readOnlyActive = true;
this.#previousToolHtmlElem = $(".whiteboard-tool.active");
// switch to mouse tool to prevent the use of the
// other tools
$(".whiteboard-tool[tool=mouse]").click();
$(".whiteboard-tool").prop("disabled", true);
$(".whiteboard-edit-group > button").prop("disabled", true);
$(".whiteboard-edit-group").addClass("group-disabled");
$("#whiteboardUnlockBtn").hide();
$("#whiteboardLockBtn").show();
}
/**
* Deactivate read-only mode
*/
deactivateReadOnlyMode() {
this.#readOnlyActive = false;
$(".whiteboard-tool").prop("disabled", false);
$(".whiteboard-edit-group > button").prop("disabled", false);
$(".whiteboard-edit-group").removeClass("group-disabled");
$("#whiteboardUnlockBtn").show();
$("#whiteboardLockBtn").hide();
// restore previously selected tool
const { previousToolHtmlElem } = this;
if (previousToolHtmlElem) previousToolHtmlElem.click();
}
}
export default new ReadOnlyService();

View File

@@ -0,0 +1,48 @@
import Point from "../classes/Point";
import { getCurrentTimeMs } from "../utils";
import ConfigService from "./ConfigService";
/**
* Class to handle all the throttling logic
*/
class ThrottlingService {
/**
* @type {number}
*/
#lastSuccessTime = 0;
get lastSuccessTime() {
return this.#lastSuccessTime;
}
/**
* @type {Point}
*/
#lastPointPosition = new Point(0, 0);
get lastPointPosition() {
return this.#lastPointPosition;
}
/**
* Helper to throttle events based on the configuration.
* Only if checks are ok, the onSuccess callback will be called.
*
* @param {Point} newPosition New point position to base the throttling on
* @param {function()} onSuccess Callback called when the throttling is successful
*/
throttle(newPosition, onSuccess) {
const newTime = getCurrentTimeMs();
const { lastPointPosition, lastSuccessTime } = this;
if (newTime - lastSuccessTime > ConfigService.pointerEventsThrottling.minTimeDelta) {
if (
lastPointPosition.distTo(newPosition) >
ConfigService.pointerEventsThrottling.minDistDelta
) {
onSuccess();
this.#lastPointPosition = newPosition;
this.#lastSuccessTime = newTime;
}
}
}
}
export default new ThrottlingService();

147
src/js/shortcutFunctions.js Normal file
View File

@@ -0,0 +1,147 @@
import whiteboard from "./whiteboard";
import ReadOnlyService from "./services/ReadOnlyService";
/**
* @param {function} callback
* @param {boolean} readOnlySensitive should the shortcut function be active event when the whiteboard is in read-only mode
*/
function defineShortcut(callback, readOnlySensitive = true) {
return () => {
if (readOnlySensitive && ReadOnlyService.readOnlyActive) return;
callback();
};
}
const shortcutFunctions = {
clearWhiteboard: defineShortcut(() => whiteboard.clearWhiteboard()),
undoStep: defineShortcut(() => whiteboard.undoWhiteboardClick()),
redoStep: defineShortcut(() => whiteboard.redoWhiteboardClick()),
setTool_mouse: defineShortcut(() => $(".whiteboard-tool[tool=mouse]").click()),
setTool_recSelect: defineShortcut(() => $(".whiteboard-tool[tool=recSelect]").click()),
setTool_pen: defineShortcut(() => {
$(".whiteboard-tool[tool=pen]").click();
whiteboard.redrawMouseCursor();
}),
setTool_line: defineShortcut(() => $(".whiteboard-tool[tool=line]").click()),
setTool_rect: defineShortcut(() => $(".whiteboard-tool[tool=rect]").click()),
setTool_circle: defineShortcut(() => $(".whiteboard-tool[tool=circle]").click()),
setTool_text: defineShortcut(() => $(".whiteboard-tool[tool=text]").click()),
setTool_eraser: defineShortcut(() => {
$(".whiteboard-tool[tool=eraser]").click();
whiteboard.redrawMouseCursor();
}),
thickness_bigger: defineShortcut(() => {
const thickness = parseInt($("#whiteboardThicknessSlider").val()) + 1;
$("#whiteboardThicknessSlider").val(thickness);
whiteboard.setStrokeThickness(thickness);
whiteboard.redrawMouseCursor();
}),
thickness_smaller: defineShortcut(() => {
const thickness = parseInt($("#whiteboardThicknessSlider").val()) - 1;
$("#whiteboardThicknessSlider").val(thickness);
whiteboard.setStrokeThickness(thickness);
whiteboard.redrawMouseCursor();
}),
openColorPicker: defineShortcut(() => $("#whiteboardColorpicker").click()),
saveWhiteboardAsImage: defineShortcut(() => $("#saveAsImageBtn").click(), false),
saveWhiteboardAsJson: defineShortcut(() => $("#saveAsJSONBtn").click(), false),
uploadWhiteboardToWebDav: defineShortcut(() => $("#uploadWebDavBtn").click()),
uploadJsonToWhiteboard: defineShortcut(() => $("#uploadJsonBtn").click()),
shareWhiteboard: defineShortcut(() => $("#shareWhiteboardBtn").click(), false),
hideShowControls: defineShortcut(() => $("#minMaxBtn").click(), false),
setDrawColorBlack: defineShortcut(() => {
whiteboard.setDrawColor("black");
whiteboard.redrawMouseCursor();
}),
setDrawColorRed: defineShortcut(() => {
whiteboard.setDrawColor("red");
whiteboard.redrawMouseCursor();
}),
setDrawColorGreen: defineShortcut(() => {
whiteboard.setDrawColor("green");
whiteboard.redrawMouseCursor();
}),
setDrawColorBlue: defineShortcut(() => {
whiteboard.setDrawColor("blue");
whiteboard.redrawMouseCursor();
}),
setDrawColorYellow: defineShortcut(() => {
whiteboard.setDrawColor("yellow");
whiteboard.redrawMouseCursor();
}),
toggleLineRecCircle: defineShortcut(() => {
const activeTool = $(".whiteboard-tool.active").attr("tool");
if (activeTool === "line") {
$(".whiteboard-tool[tool=rect]").click();
} else if (activeTool === "rect") {
$(".whiteboard-tool[tool=circle]").click();
} else {
$(".whiteboard-tool[tool=line]").click();
}
}),
togglePenEraser: defineShortcut(() => {
const activeTool = $(".whiteboard-tool.active").attr("tool");
if (activeTool === "pen") {
$(".whiteboard-tool[tool=eraser]").click();
} else {
$(".whiteboard-tool[tool=pen]").click();
}
}),
toggleMainColors: defineShortcut(() => {
const bgColor = $("#whiteboardColorpicker")[0].style.backgroundColor;
if (bgColor === "blue") {
shortcutFunctions.setDrawColorGreen();
} else if (bgColor === "green") {
shortcutFunctions.setDrawColorYellow();
} else if (bgColor === "yellow") {
shortcutFunctions.setDrawColorRed();
} else if (bgColor === "red") {
shortcutFunctions.setDrawColorBlack();
} else {
shortcutFunctions.setDrawColorBlue();
}
}),
moveDraggableUp: defineShortcut(() => {
const elm =
whiteboard.tool === "text"
? $("#" + whiteboard.latestActiveTextBoxId)
: $(".dragMe")[0];
const p = $(elm).position();
if (p) $(elm).css({ top: p.top - 5, left: p.left });
}),
moveDraggableDown: defineShortcut(() => {
const elm =
whiteboard.tool === "text"
? $("#" + whiteboard.latestActiveTextBoxId)
: $(".dragMe")[0];
const p = $(elm).position();
if (p) $(elm).css({ top: p.top + 5, left: p.left });
}),
moveDraggableLeft: defineShortcut(() => {
const elm =
whiteboard.tool === "text"
? $("#" + whiteboard.latestActiveTextBoxId)
: $(".dragMe")[0];
const p = $(elm).position();
if (p) $(elm).css({ top: p.top, left: p.left - 5 });
}),
moveDraggableRight: defineShortcut(() => {
const elm =
whiteboard.tool === "text"
? $("#" + whiteboard.latestActiveTextBoxId)
: $(".dragMe")[0];
const p = $(elm).position();
if (p) $(elm).css({ top: p.top, left: p.left + 5 });
}),
dropDraggable: defineShortcut(() => {
$($(".dragMe")[0]).find(".addToCanvasBtn").click();
}),
addToBackground: defineShortcut(() => {
$($(".dragMe")[0]).find(".addToBackgroundBtn").click();
}),
cancelAllActions: defineShortcut(() => whiteboard.escKeyAction()),
deleteSelection: defineShortcut(() => whiteboard.delKeyAction()),
};
export default shortcutFunctions;

44
src/js/utils.js Normal file
View File

@@ -0,0 +1,44 @@
/**
* Compute the euclidean distance between two points
* @param {Point} p1
* @param {Point} p2
*/
export function computeDist(p1, p2) {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
/**
* Return the current time in ms since 1970
* @returns {number}
*/
export function getCurrentTimeMs() {
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;
}

File diff suppressed because it is too large Load Diff