Merge branch 'master' into master

This commit is contained in:
Adam Macumber 2023-11-27 09:52:48 -05:00 committed by GitHub
commit 05d0ea2d2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 3721 additions and 3246 deletions

View file

@ -5,13 +5,12 @@ name: Node.js CI
on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
strategy:
@ -29,3 +28,16 @@ jobs:
- run: npm test
env:
CI: true
format:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies
run: npm install
- name: Run Prettier check
run: npx prettier --check .

5
.prettierignore Normal file
View file

@ -0,0 +1,5 @@
package.json
package-lock.json
node_modules/
.DS_Store
*.html

12
.prettierrc Normal file
View file

@ -0,0 +1,12 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"endOfLine": "lf",
"insertPragma": false,
"proseWrap": "preserve",
"requirePragma": false,
"tabWidth": 2,
"useTabs": false,
"printWidth": 80
}

View file

@ -45,6 +45,7 @@ You can then access WBO at `http://localhost:5001`.
Alternatively, you can run the code with [node.js](https://nodejs.org/) directly, without docker.
First, download the sources:
```
git clone https://github.com/lovasoa/whitebophir.git
cd whitebophir
@ -58,14 +59,17 @@ npm install --production
```
Finally, you can start the server:
```
PORT=5001 npm start
```
This will run WBO directly on your machine, on port 5001, without any isolation from the other services. You can also use an invokation like
```
PORT=5001 HOST=127.0.0.1 npm start
```
to make whitebophir only listen on the loopback device. This is useful if you want to put whitebophir behind a reverse proxy.
### Running WBO on a subfolder
@ -88,8 +92,8 @@ The `AUTH_SECRET_KEY` variable in [`configuration.js`](./server/configuration.js
Within the payload, you can declare the user's roles as an array.
Currently the only accepted roles are `moderator` and `editor`.
- `moderator` will give the user an additional tool to wipe all data from the board. To declare this role, see the example below.
- `editor` will give the user the ability to edit the board. This is the default role for all users.
- `moderator` will give the user an additional tool to wipe all data from the board. To declare this role, see the example below.
- `editor` will give the user the ability to edit the board. This is the default role for all users.
```json
{
@ -98,6 +102,7 @@ Currently the only accepted roles are `moderator` and `editor`.
"roles": ["moderator"]
}
```
Moderators have access to the Clear tool, which will wipe all content from the board.
## Board name verification in the JWT
@ -108,9 +113,15 @@ To check for a valid board name just add the board name to the role with a ":".
```json
{
"roles": ["moderator:<boardName1>","moderator:<boardName2>","editor:<boardName3>","editor:<boardName4>"]
"roles": [
"moderator:<boardName1>",
"moderator:<boardName2>",
"editor:<boardName3>",
"editor:<boardName4>"
]
}
```
eg, `http://myboard.com/boards/mySecretBoardName?token={token}`
```json
@ -128,9 +139,10 @@ You can now be sure that only users who have the correct token have access to th
When you start a WBO server, it loads its configuration from several environment variables.
You can see a list of these variables in [`configuration.js`](./server/configuration.js).
Some important environment variables are :
- `WBO_HISTORY_DIR` : configures the directory where the boards are saved. Defaults to `./server-data/`.
- `WBO_MAX_EMIT_COUNT` : the maximum number of messages that a client can send per unit of time. Increase this value if you want smoother drawings, at the expense of being susceptible to denial of service attacks if your server does not have enough processing power. By default, the units of this quantity are messages per 4 seconds, and the default value is `192`.
- `AUTH_SECRET_KEY` : If you would like to authenticate your boards using jwt, this declares the secret key.
- `WBO_HISTORY_DIR` : configures the directory where the boards are saved. Defaults to `./server-data/`.
- `WBO_MAX_EMIT_COUNT` : the maximum number of messages that a client can send per unit of time. Increase this value if you want smoother drawings, at the expense of being susceptible to denial of service attacks if your server does not have enough processing power. By default, the units of this quantity are messages per 4 seconds, and the default value is `192`.
- `AUTH_SECRET_KEY` : If you would like to authenticate your boards using jwt, this declares the secret key.
## Troubleshooting
@ -146,5 +158,5 @@ metrics collection agent.
Example: `docker run -e STATSD_URL=udp://127.0.0.1:8125 lovasoa/wbo`.
- If you use **prometheus**, you can collect the metrics with [statsd-exporter](https://hub.docker.com/r/prom/statsd-exporter).
- If you use **datadog**, you can collect the metrics with [dogstatsd](https://docs.datadoghq.com/developers/dogstatsd).
- If you use **prometheus**, you can collect the metrics with [statsd-exporter](https://hub.docker.com/r/prom/statsd-exporter).
- If you use **datadog**, you can collect the metrics with [dogstatsd](https://docs.datadoghq.com/developers/dogstatsd).

View file

@ -1,7 +1,11 @@
html, body, svg {
padding:0;
margin:0;
font-family: Liberation sans, sans-serif;
html,
body,
svg {
padding: 0;
margin: 0;
font-family:
Liberation sans,
sans-serif;
}
#canvas {
@ -16,7 +20,7 @@ html, body, svg {
line-height: 50px;
text-align: center;
border-radius: 10px;
position:fixed;
position: fixed;
top: 40%;
left: 30%;
z-index: 1;
@ -57,29 +61,30 @@ html, body, svg {
}
#menu.closed {
border-radius:3px;
left:10px;
top:10px;
background-color:rgba(100,200,255,0.7);
width:6vw;
height:2em;
transition-duration:1s;
border-radius: 3px;
left: 10px;
top: 10px;
background-color: rgba(100, 200, 255, 0.7);
width: 6vw;
height: 2em;
transition-duration: 1s;
}
#menu h2{ /*Menu title ("Menu")*/
#menu h2 {
/*Menu title ("Menu")*/
display: none;
font-size:4vh;
font-size: 4vh;
text-align: center;
letter-spacing:.5vw;
letter-spacing: 0.5vw;
text-shadow: 0px 0px 5px white;
color:black;
padding:0;
margin:0;
color: black;
padding: 0;
margin: 0;
}
#menu .tools {
list-style-type:none;
padding:0;
list-style-type: none;
padding: 0;
}
#settings {
@ -97,20 +102,20 @@ html, body, svg {
supported by Chrome, Opera and Firefox */
pointer-events: auto;
white-space: nowrap;
list-style-position:inside;
border:1px solid #eeeeee;
text-decoration:none;
cursor:pointer;
list-style-position: inside;
border: 1px solid #eeeeee;
text-decoration: none;
cursor: pointer;
background: #ffffff;
margin-top: 10px;
height: 40px;
line-height: 40px;
border-radius: 0px;
max-width: 40px;
transition-duration: .2s;
transition-duration: 0.2s;
overflow: hidden;
width: max-content;
box-shadow: inset 0 0 3px #8FA2BC;
box-shadow: inset 0 0 3px #8fa2bc;
}
#menu .tool:hover {
@ -133,27 +138,26 @@ html, body, svg {
#menu:focus-within {
pointer-events: none;
}
}
#menu .oneTouch:active {
border-radius: 3px;
background-color:#eeeeff;
background-color: #eeeeff;
}
#menu .tool:active {
box-shadow: inset 0 0 1px #ddeeff;
background-color:#eeeeff;
background-color: #eeeeff;
}
#menu .tool.curTool {
box-shadow: 0 0 5px #0074D9;
background: linear-gradient(#96E1FF, #36A2FF);
box-shadow: 0 0 5px #0074d9;
background: linear-gradient(#96e1ff, #36a2ff);
}
#menu .tool-icon {
display: inline-block;
text-align:center;
text-align: center;
width: 35px;
height: 35px;
margin: 2.5px;
@ -183,23 +187,22 @@ html, body, svg {
display: inline-block;
width: 150px;
height: 30px;
font-size: .9em;
font-size: 0.9em;
line-height: 15px;
vertical-align: top;
padding: 6px;
}
#menu .tool.hasSecondary .tool-icon{
margin-top:0px;
margin-left:0px;
#menu .tool.hasSecondary .tool-icon {
margin-top: 0px;
margin-left: 0px;
}
#menu .tool .tool-icon.secondaryIcon{
#menu .tool .tool-icon.secondaryIcon {
display: none;
}
#menu .tool.hasSecondary .tool-icon.secondaryIcon{
#menu .tool.hasSecondary .tool-icon.secondaryIcon {
display: block;
position: absolute;
bottom: 0px;
@ -209,15 +212,15 @@ html, body, svg {
}
input {
font-size:16px;
font-size: 16px;
}
#chooseColor {
width: 100%;
height:100%;
height: 100%;
border: 0;
border-radius: 0;
color:black;
color: black;
display: block;
margin: 0;
padding: 0;
@ -261,14 +264,14 @@ path {
}
text {
font-family:"Arial", "Helvetica", sans-serif;
user-select:none;
-moz-user-select:none;
font-family: "Arial", "Helvetica", sans-serif;
user-select: none;
-moz-user-select: none;
}
circle.opcursor {
pointer-events: none;
transition: .1s;
transition: 0.1s;
}
#cursor-me {
@ -280,7 +283,7 @@ circle.opcursor {
#chooseColor {
color: transparent;
}
label.tool-name[for=chooseColor] {
label.tool-name[for="chooseColor"] {
line-height: 10px;
}
}

View file

@ -234,7 +234,10 @@ footer a:hover {
width: 300px;
box-shadow: -150px 0 150px 150px black;
background: black;
transition: width 0.5s, box-shadow 0.3s ease 0.2s, background-color 0.5s;
transition:
width 0.5s,
box-shadow 0.3s ease 0.2s,
background-color 0.5s;
}
.lang-selector ul {
@ -253,7 +256,10 @@ footer a:hover {
text-align: right;
text-transform: uppercase;
list-style: none;
transition: box-shadow 0.3s, width 0.5s, background-color 0.5s ease 0.3s;
transition:
box-shadow 0.3s,
width 0.5s,
background-color 0.5s ease 0.3s;
}
.lang-selector li a {

View file

@ -29,10 +29,10 @@ var Tools = {};
Tools.i18n = (function i18n() {
var translations = JSON.parse(document.getElementById("translations").text);
return {
"t": function translate(s) {
var key = s.toLowerCase().replace(/ /g, '_');
t: function translate(s) {
var key = s.toLowerCase().replace(/ /g, "_");
return translations[key] || s;
}
},
};
})();
@ -66,16 +66,16 @@ Tools.connect = function () {
var params = new URLSearchParams(url.search);
var socket_params = {
"path": window.location.pathname.split("/boards/")[0] + "/socket.io",
"reconnection": true,
"reconnectionDelay": 100, //Make the xhr connections as fast as possible
"timeout": 1000 * 60 * 20 // Timeout after 20 minutes
}
if(params.has("token")) {
path: window.location.pathname.split("/boards/")[0] + "/socket.io",
reconnection: true,
reconnectionDelay: 100, //Make the xhr connections as fast as possible
timeout: 1000 * 60 * 20, // Timeout after 20 minutes
};
if (params.has("token")) {
socket_params.query = "token=" + params.get("token");
}
this.socket = io.connect('', socket_params);
this.socket = io.connect("", socket_params);
//Receive draw instructions from the server
this.socket.on("broadcast", function (msg) {
@ -86,7 +86,7 @@ Tools.connect = function () {
});
this.socket.on("reconnect", function onReconnection() {
Tools.socket.emit('joinboard', Tools.boardName);
Tools.socket.emit("joinboard", Tools.boardName);
});
};
@ -97,7 +97,7 @@ Tools.boardName = (function () {
return decodeURIComponent(path[path.length - 1]);
})();
Tools.token = (function() {
Tools.token = (function () {
var url = new URL(window.location);
var params = new URLSearchParams(url.search);
return params.get("token");
@ -108,12 +108,13 @@ Tools.socket.emit("getboard", Tools.boardName);
function saveBoardNametoLocalStorage() {
var boardName = Tools.boardName;
if (boardName.toLowerCase() === 'anonymous') return;
var recentBoards, key = "recent-boards";
if (boardName.toLowerCase() === "anonymous") return;
var recentBoards,
key = "recent-boards";
try {
recentBoards = JSON.parse(localStorage.getItem(key));
if (!Array.isArray(recentBoards)) throw new Error("Invalid type");
} catch(e) {
} catch (e) {
// On localstorage or json error, reset board list
recentBoards = [];
console.log("Board history loading error", e);
@ -148,19 +149,25 @@ Tools.HTML = {
return this.template.add(function (elem) {
elem.addEventListener("click", callback);
elem.id = "toolID-" + toolName;
elem.getElementsByClassName("tool-name")[0].textContent = Tools.i18n.t(toolName);
elem.getElementsByClassName("tool-name")[0].textContent =
Tools.i18n.t(toolName);
var toolIconElem = elem.getElementsByClassName("tool-icon")[0];
toolIconElem.src = toolIcon;
toolIconElem.alt = toolIcon;
if (oneTouch) elem.classList.add("oneTouch");
elem.title =
Tools.i18n.t(toolName) + " (" +
Tools.i18n.t("keyboard shortcut") + ": " +
toolShortcut + ")" +
(Tools.list[toolName].secondary ? " [" + Tools.i18n.t("click_to_toggle") + "]" : "");
Tools.i18n.t(toolName) +
" (" +
Tools.i18n.t("keyboard shortcut") +
": " +
toolShortcut +
")" +
(Tools.list[toolName].secondary
? " [" + Tools.i18n.t("click_to_toggle") + "]"
: "");
if (Tools.list[toolName].secondary) {
elem.classList.add('hasSecondary');
var secondaryIcon = elem.getElementsByClassName('secondaryIcon')[0];
elem.classList.add("hasSecondary");
var secondaryIcon = elem.getElementsByClassName("secondaryIcon")[0];
secondaryIcon.src = Tools.list[toolName].secondary.icon;
toolIconElem.classList.add("primaryIcon");
}
@ -185,7 +192,8 @@ Tools.HTML = {
// Change primary icon
elem.getElementsByClassName("tool-icon")[0].src = icon;
elem.getElementsByClassName("tool-name")[0].textContent = Tools.i18n.t(name);
elem.getElementsByClassName("tool-name")[0].textContent =
Tools.i18n.t(name);
},
addStylesheet: function (href) {
//Adds a css stylesheet to the html or svg document
@ -201,19 +209,20 @@ Tools.HTML = {
if (button.key) this.addShortcut(button.key, setColor);
return this.colorPresetTemplate.add(function (elem) {
elem.addEventListener("click", setColor);
elem.id = "color_" + button.color.replace(/^#/, '');
elem.id = "color_" + button.color.replace(/^#/, "");
elem.style.backgroundColor = button.color;
if (button.key) {
elem.title = Tools.i18n.t("keyboard shortcut") + ": " + button.key;
}
});
}
},
};
Tools.list = {}; // An array of all known tools. {"toolName" : {toolObject}}
Tools.isBlocked = function toolIsBanned(tool) {
if (tool.name.includes(",")) throw new Error("Tool Names must not contain a comma");
if (tool.name.includes(","))
throw new Error("Tool Names must not contain a comma");
return Tools.server_config.BLOCKED_TOOLS.includes(tool.name);
};
@ -224,8 +233,12 @@ Tools.register = function registerTool(newTool) {
if (Tools.isBlocked(newTool)) return;
if (newTool.name in Tools.list) {
console.log("Tools.add: The tool '" + newTool.name + "' is already" +
"in the list. Updating it...");
console.log(
"Tools.add: The tool '" +
newTool.name +
"' is already" +
"in the list. Updating it...",
);
}
//Format the new tool correctly
@ -242,7 +255,7 @@ Tools.register = function registerTool(newTool) {
if (pending) {
console.log("Drawing pending messages for '%s'.", newTool.name);
var msg;
while (msg = pending.shift()) {
while ((msg = pending.shift())) {
//Transmit the message to the tool (precising that it comes from the network)
newTool.draw(msg, false);
}
@ -262,13 +275,20 @@ Tools.add = function (newTool) {
}
//Add the tool to the GUI
Tools.HTML.addTool(newTool.name, newTool.icon, newTool.iconHTML, newTool.shortcut, newTool.oneTouch);
Tools.HTML.addTool(
newTool.name,
newTool.icon,
newTool.iconHTML,
newTool.shortcut,
newTool.oneTouch,
);
};
Tools.change = function (toolName) {
var newTool = Tools.list[toolName];
var oldTool = Tools.curTool;
if (!newTool) throw new Error("Trying to select a tool that has never been added!");
if (!newTool)
throw new Error("Trying to select a tool that has never been added!");
if (newTool === oldTool) {
if (newTool.secondary) {
newTool.secondary.active = !newTool.secondary.active;
@ -280,7 +300,7 @@ Tools.change = function (toolName) {
}
if (!newTool.oneTouch) {
//Update the GUI
var curToolName = (Tools.curTool) ? Tools.curTool.name : "";
var curToolName = Tools.curTool ? Tools.curTool.name : "";
try {
Tools.HTML.changeTool(curToolName, toolName);
} catch (e) {
@ -314,7 +334,7 @@ Tools.addToolListeners = function addToolListeners(tool) {
for (var event in tool.compiledListeners) {
var listener = tool.compiledListeners[event];
var target = listener.target || Tools.board;
target.addEventListener(event, listener, { 'passive': false });
target.addEventListener(event, listener, { passive: false });
}
};
@ -331,7 +351,11 @@ Tools.removeToolListeners = function removeToolListeners(tool) {
(function () {
// Handle secondary tool switch with shift (key code 16)
function handleShift(active, evt) {
if (evt.keyCode === 16 && Tools.curTool.secondary && Tools.curTool.secondary.active !== active) {
if (
evt.keyCode === 16 &&
Tools.curTool.secondary &&
Tools.curTool.secondary.active !== active
) {
Tools.change(Tools.curTool.name);
}
}
@ -345,10 +369,10 @@ Tools.send = function (data, toolName) {
d.tool = toolName;
Tools.applyHooks(Tools.messageHooks, d);
var message = {
"board": Tools.boardName,
"data": d
board: Tools.boardName,
data: d,
};
Tools.socket.emit('broadcast', message);
Tools.socket.emit("broadcast", message);
};
Tools.drawAndSend = function (data, tool) {
@ -376,9 +400,14 @@ function messageForTool(message) {
else Tools.pendingMessages[name].push(message);
}
if (message.tool !== 'Hand' && message.transform != null) {
if (message.tool !== "Hand" && message.transform != null) {
//this message has special info for the mover
messageForTool({ tool: 'Hand', type: 'update', transform: message.transform, id: message.id});
messageForTool({
tool: "Hand",
type: "update",
transform: message.transform,
id: message.id,
});
}
}
@ -393,7 +422,8 @@ function batchCall(fn, args) {
return Promise.all(batch.map(fn))
.then(function () {
return new Promise(requestAnimationFrame);
}).then(batchCall.bind(null, fn, rest));
})
.then(batchCall.bind(null, fn, rest));
}
}
@ -421,14 +451,15 @@ window.addEventListener("focus", function () {
function updateDocumentTitle() {
document.title =
(Tools.unreadMessagesCount ? '(' + Tools.unreadMessagesCount + ') ' : '') +
(Tools.unreadMessagesCount ? "(" + Tools.unreadMessagesCount + ") " : "") +
Tools.boardName +
" | WBO";
}
(function () {
// Scroll and hash handling
var scrollTimeout, lastStateUpdate = Date.now();
var scrollTimeout,
lastStateUpdate = Date.now();
window.addEventListener("scroll", function onScroll() {
var scale = Tools.getScale();
@ -437,8 +468,12 @@ function updateDocumentTitle() {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(function updateHistory() {
var hash = '#' + (x | 0) + ',' + (y | 0) + ',' + Tools.getScale().toFixed(1);
if (Date.now() - lastStateUpdate > 5000 && hash !== window.location.hash) {
var hash =
"#" + (x | 0) + "," + (y | 0) + "," + Tools.getScale().toFixed(1);
if (
Date.now() - lastStateUpdate > 5000 &&
hash !== window.location.hash
) {
window.history.pushState({}, "", hash);
lastStateUpdate = Date.now();
} else {
@ -448,7 +483,7 @@ function updateDocumentTitle() {
});
function setScrollFromHash() {
var coords = window.location.hash.slice(1).split(',');
var coords = window.location.hash.slice(1).split(",");
var x = coords[0] | 0;
var y = coords[1] | 0;
var scale = parseFloat(coords[2]);
@ -464,7 +499,8 @@ function updateDocumentTitle() {
function resizeCanvas(m) {
//Enlarge the canvas whenever something is drawn near its border
var x = m.x | 0, y = m.y | 0
var x = m.x | 0,
y = m.y | 0;
var MAX_BOARD_SIZE = Tools.server_config.MAX_BOARD_SIZE || 65536; // Maximum value for any x or y on the board
if (x > Tools.svg.width.baseVal.value - 2000) {
Tools.svg.width.baseVal.value = Math.min(x + 2000, MAX_BOARD_SIZE);
@ -486,36 +522,38 @@ Tools.messageHooks = [resizeCanvas, updateUnreadCount];
Tools.scale = 1.0;
var scaleTimeout = null;
Tools.setScale = function setScale(scale) {
var fullScale = Math.max(window.innerWidth, window.innerHeight) / Tools.server_config.MAX_BOARD_SIZE;
var fullScale =
Math.max(window.innerWidth, window.innerHeight) /
Tools.server_config.MAX_BOARD_SIZE;
var minScale = Math.max(0.1, fullScale);
var maxScale = 10;
if (isNaN(scale)) scale = 1;
scale = Math.max(minScale, Math.min(maxScale, scale));
Tools.svg.style.willChange = 'transform';
Tools.svg.style.transform = 'scale(' + scale + ')';
Tools.svg.style.willChange = "transform";
Tools.svg.style.transform = "scale(" + scale + ")";
clearTimeout(scaleTimeout);
scaleTimeout = setTimeout(function () {
Tools.svg.style.willChange = 'auto';
Tools.svg.style.willChange = "auto";
}, 1000);
Tools.scale = scale;
return scale;
}
};
Tools.getScale = function getScale() {
return Tools.scale;
}
};
//List of hook functions that will be applied to tools before adding them
Tools.toolHooks = [
function checkToolAttributes(tool) {
if (typeof (tool.name) !== "string") throw "A tool must have a name";
if (typeof (tool.listeners) !== "object") {
if (typeof tool.name !== "string") throw "A tool must have a name";
if (typeof tool.listeners !== "object") {
tool.listeners = {};
}
if (typeof (tool.onstart) !== "function") {
tool.onstart = function () { };
if (typeof tool.onstart !== "function") {
tool.onstart = function () {};
}
if (typeof (tool.onquit) !== "function") {
tool.onquit = function () { };
if (typeof tool.onquit !== "function") {
tool.onquit = function () {};
}
},
function compileListeners(tool) {
@ -526,16 +564,18 @@ Tools.toolHooks = [
var compiled = tool.compiledListeners || {};
tool.compiledListeners = compiled;
function compile(listener) { //closure
return (function listen(evt) {
function compile(listener) {
//closure
return function listen(evt) {
var x = evt.pageX / Tools.getScale(),
y = evt.pageY / Tools.getScale();
return listener(x, y, evt, false);
});
};
}
function compileTouch(listener) { //closure
return (function touchListen(evt) {
function compileTouch(listener) {
//closure
return function touchListen(evt) {
//Currently, we don't handle multitouch
if (evt.changedTouches.length === 1) {
//evt.preventDefault();
@ -545,19 +585,27 @@ Tools.toolHooks = [
return listener(x, y, evt, true);
}
return true;
});
};
}
function wrapUnsetHover(f, toolName) {
return (function unsetHover(evt) {
document.activeElement && document.activeElement.blur && document.activeElement.blur();
return function unsetHover(evt) {
document.activeElement &&
document.activeElement.blur &&
document.activeElement.blur();
return f(evt);
});
};
}
if (listeners.press) {
compiled["mousedown"] = wrapUnsetHover(compile(listeners.press), tool.name);
compiled["touchstart"] = wrapUnsetHover(compileTouch(listeners.press), tool.name);
compiled["mousedown"] = wrapUnsetHover(
compile(listeners.press),
tool.name,
);
compiled["touchstart"] = wrapUnsetHover(
compileTouch(listeners.press),
tool.name,
);
}
if (listeners.move) {
compiled["mousemove"] = compile(listeners.move);
@ -572,7 +620,7 @@ Tools.toolHooks = [
compiled["touchend"] = releaseTouch;
compiled["touchcancel"] = releaseTouch;
}
}
},
];
Tools.applyHooks = function (hooks, object) {
@ -582,12 +630,11 @@ Tools.applyHooks = function (hooks, object) {
});
};
// Utility functions
Tools.generateUID = function (prefix, suffix) {
var uid = Date.now().toString(36); //Create the uids in chronological order
uid += (Math.round(Math.random() * 36)).toString(36); //Add a random character at the end
uid += Math.round(Math.random() * 36).toString(36); //Add a random character at the end
if (prefix) uid = prefix + uid;
if (suffix) uid = uid + suffix;
return uid;
@ -595,7 +642,7 @@ Tools.generateUID = function (prefix, suffix) {
Tools.createSVGElement = function createSVGElement(name, attrs) {
var elem = document.createElementNS(Tools.svg.namespaceURI, name);
if (typeof (attrs) !== "object") return elem;
if (typeof attrs !== "object") return elem;
Object.keys(attrs).forEach(function (key, i) {
elem.setAttributeNS(null, key, attrs[key]);
});
@ -608,17 +655,17 @@ Tools.positionElement = function (elem, x, y) {
};
Tools.colorPresets = [
{ color: "#001f3f", key: '1' },
{ color: "#FF4136", key: '2' },
{ color: "#0074D9", key: '3' },
{ color: "#FF851B", key: '4' },
{ color: "#FFDC00", key: '5' },
{ color: "#3D9970", key: '6' },
{ color: "#91E99B", key: '7' },
{ color: "#90468b", key: '8' },
{ color: "#7FDBFF", key: '9' },
{ color: "#AAAAAA", key: '0' },
{ color: "#E65194" }
{ color: "#001f3f", key: "1" },
{ color: "#FF4136", key: "2" },
{ color: "#0074D9", key: "3" },
{ color: "#FF851B", key: "4" },
{ color: "#FFDC00", key: "5" },
{ color: "#3D9970", key: "6" },
{ color: "#91E99B", key: "7" },
{ color: "#90468b", key: "8" },
{ color: "#7FDBFF", key: "9" },
{ color: "#AAAAAA", key: "0" },
{ color: "#E65194" },
];
Tools.color_chooser = document.getElementById("chooseColor");
@ -631,7 +678,9 @@ Tools.getColor = (function color() {
var color_index = (Math.random() * Tools.colorPresets.length) | 0;
var initial_color = Tools.colorPresets[color_index].color;
Tools.setColor(initial_color);
return function () { return Tools.color_chooser.value; };
return function () {
return Tools.color_chooser.value;
};
})();
Tools.colorPresets.forEach(Tools.HTML.addColorButton.bind(Tools.HTML));
@ -651,12 +700,17 @@ Tools.setSize = (function size() {
chooser.onchange = chooser.oninput = update;
return function (value) {
if (value !== null && value !== undefined) { chooser.value = value; update(); }
if (value !== null && value !== undefined) {
chooser.value = value;
update();
}
return parseInt(chooser.value);
};
})();
Tools.getSize = (function () { return Tools.setSize() });
Tools.getSize = function () {
return Tools.setSize();
};
Tools.getOpacity = (function opacity() {
var chooser = document.getElementById("chooseOpacity");
@ -673,7 +727,6 @@ Tools.getOpacity = (function opacity() {
};
})();
//Scale the canvas on load
Tools.svg.width.baseVal.value = document.body.clientWidth;
Tools.svg.height.baseVal.value = document.body.clientHeight;
@ -696,15 +749,14 @@ Tools.svg.height.baseVal.value = document.body.clientHeight;
}
*/
(function () {
var pos = {top: 0, scroll:0};
var pos = { top: 0, scroll: 0 };
var menu = document.getElementById("menu");
function menu_mousedown(evt) {
pos = {
top: menu.scrollTop,
scroll: evt.clientY
}
scroll: evt.clientY,
};
menu.addEventListener("mousemove", menu_mousemove);
document.addEventListener("mouseup", menu_mouseup);
}
@ -717,4 +769,4 @@ Tools.svg.height.baseVal.value = document.body.clientHeight;
document.removeEventListener("mouseup", menu_mouseup);
}
menu.addEventListener("mousedown", menu_mousedown);
})()
})();

View file

@ -24,7 +24,6 @@
* @licend
*/
/*jshint bitwise:false*/
// ==ClosureCompiler==
@ -35,12 +34,14 @@
// @use_types_for_optimization true
// ==/ClosureCompiler==
var canvascolor = (function() {//Code Isolation
var canvascolor = (function () {
//Code Isolation
"use strict";
(function addCSS () {
(function addCSS() {
var styleTag = document.createElement("style");
styleTag.innerHTML = [".canvascolor-container{",
styleTag.innerHTML = [
".canvascolor-container{",
"background-color:black;",
"border-radius:5px;",
"overflow:hidden;",
@ -57,38 +58,45 @@ var canvascolor = (function() {//Code Isolation
".canvascolor-history > div{",
"margin:2px;",
"display:inline-block;",
"}"].join("");
"}",
].join("");
document.head.appendChild(styleTag);
})();
function hsv2rgb (h,s,v) {
if( s === 0 ) return [v,v,v]; // achromatic (grey)
function hsv2rgb(h, s, v) {
if (s === 0) return [v, v, v]; // achromatic (grey)
h /= (Math.PI/6); // sector 0 to 5
var i = h|0,
h /= Math.PI / 6; // sector 0 to 5
var i = h | 0,
f = h - i, // factorial part of h
p = v * ( 1 - s ),
q = v * ( 1 - s * f ),
t = v * ( 1 - s * ( 1 - f ) );
switch( i%6 ) {
case 0: return [v,t,p];
case 1: return [q,v,p];
case 2: return [p,v,t];
case 3: return [p,q,v];
case 4: return [t,p,v];
case 5:return [v,p,q];
p = v * (1 - s),
q = v * (1 - s * f),
t = v * (1 - s * (1 - f));
switch (i % 6) {
case 0:
return [v, t, p];
case 1:
return [q, v, p];
case 2:
return [p, v, t];
case 3:
return [p, q, v];
case 4:
return [t, p, v];
case 5:
return [v, p, q];
}
}
function isFixedPosition(elem) {
do {
if (getComputedStyle(elem).position === "fixed") return true;
} while ( (elem = elem.parentElement) !== null );
} while ((elem = elem.parentElement) !== null);
return false;
}
var containerTemplate;
(function createContainer(){
(function createContainer() {
containerTemplate = document.createElement("div");
containerTemplate.className = "canvascolor-container";
var canvas = document.createElement("canvas");
@ -101,18 +109,22 @@ var canvascolor = (function() {//Code Isolation
function canvascolor(elem) {
var curcolor = elem.value || "#000";
var w=200, h=w/2;
var w = 200,
h = w / 2;
var container = containerTemplate.cloneNode(true);
container.style.width = w+"px";
container.style.width = w + "px";
container.style.position = isFixedPosition(elem) ? "fixed" : "absolute";
var canvas = container.getElementsByTagName("canvas")[0];
var ctx = canvas.getContext("2d");
canvas.width = w; canvas.height=h;
canvas.width = w;
canvas.height = h;
var prevcolorsDiv = container.getElementsByClassName("canvascolor-history")[0];
prevcolorsDiv.style.width=w+"px";
prevcolorsDiv.style.maxHeight=h+"px";
var prevcolorsDiv = container.getElementsByClassName(
"canvascolor-history",
)[0];
prevcolorsDiv.style.width = w + "px";
prevcolorsDiv.style.maxHeight = h + "px";
var previewdiv = createColorDiv(curcolor);
previewdiv.style.border = "1px solid white";
@ -120,59 +132,65 @@ var canvascolor = (function() {//Code Isolation
document.body.appendChild(container);
function displayContainer(){
function displayContainer() {
var rect = elem.getBoundingClientRect();
var conttop=(rect.top+rect.height+3),
contleft=rect.left;
var conttop = rect.top + rect.height + 3,
contleft = rect.left;
if (container.style.position !== "fixed") {
conttop += document.documentElement.scrollTop;
contleft += document.documentElement.scrollLeft;
}
container.style.top = conttop+"px";
container.style.left = contleft+"px";
container.style.top = conttop + "px";
container.style.left = contleft + "px";
container.style.display = "block";
}
function hideContainer(){
function hideContainer() {
container.style.display = "none";
}
elem.addEventListener("mouseover", displayContainer, true);
container.addEventListener("mouseleave", hideContainer, false);
elem.addEventListener("keyup", function(){
elem.addEventListener(
"keyup",
function () {
changeColor(elem.value, true);
}, true);
},
true,
);
changeColor(elem.value, true);
var idata = ctx.createImageData(w,h);
var idata = ctx.createImageData(w, h);
function rgb2hex (rgb) {
function num2hex (c) {return (c*15/255|0).toString(16);}
return "#"+num2hex(rgb[0])+num2hex(rgb[1])+num2hex(rgb[2]);
function rgb2hex(rgb) {
function num2hex(c) {
return (((c * 15) / 255) | 0).toString(16);
}
return "#" + num2hex(rgb[0]) + num2hex(rgb[1]) + num2hex(rgb[2]);
}
function colorAt(coords) {
var x=coords[0], y=coords[1];
return hsv2rgb(x/w*Math.PI, 1, (1-y/h)*255);
var x = coords[0],
y = coords[1];
return hsv2rgb((x / w) * Math.PI, 1, (1 - y / h) * 255);
}
function render() {
for (var x=0; x<w; x++) {
for (var y=0;y<h; y++) {
var i = 4*(x+y*w);
var rgb = colorAt([x,y]);
idata.data[i] = rgb[0];//Red
idata.data[i+1] = rgb[1];//Green
idata.data[i+2] = rgb[2];//Blue
idata.data[i+3] = 255;
for (var x = 0; x < w; x++) {
for (var y = 0; y < h; y++) {
var i = 4 * (x + y * w);
var rgb = colorAt([x, y]);
idata.data[i] = rgb[0]; //Red
idata.data[i + 1] = rgb[1]; //Green
idata.data[i + 2] = rgb[2]; //Blue
idata.data[i + 3] = 255;
}
}
ctx.putImageData(idata,0,0);
ctx.putImageData(idata, 0, 0);
}
render();
/** Changes the current color (the value of the input field) and updates other variables accordingly
* @param {string} color The new color. Must be a valid CSS color string if ensureValid is not specified
* @param {boolean} [ensureValid=false] Do not make the change if color is not a valid CSS color
@ -189,16 +207,20 @@ var canvascolor = (function() {//Code Isolation
elem.focus();
}
function createColorDiv (color) {
function createColorDiv(color) {
var div = document.createElement("div");
div.style.width = (w/3-10)+"px";
div.style.height = (h/3-8)+"px";
div.style.width = w / 3 - 10 + "px";
div.style.height = h / 3 - 8 + "px";
div.style.backgroundColor = color;
div.addEventListener("click", function(){
div.addEventListener(
"click",
function () {
changeColor(color);
}, true);
},
true,
);
if (prevcolorsDiv.childElementCount <= 1) prevcolorsDiv.appendChild(div);
else prevcolorsDiv.insertBefore(div,prevcolorsDiv.children[1]);
else prevcolorsDiv.insertBefore(div, prevcolorsDiv.children[1]);
return div;
}
@ -207,36 +229,51 @@ var canvascolor = (function() {//Code Isolation
return [evt.clientX - canvasrect.left, evt.clientY - canvasrect.top];
}
canvas.addEventListener("mousemove", function(evt){
canvas.addEventListener(
"mousemove",
function (evt) {
var coords = canvasPos(evt);
previewdiv.style.backgroundColor = rgb2hex(colorAt(coords));
}, true);
},
true,
);
canvas.addEventListener("click", function(evt){
canvas.addEventListener(
"click",
function (evt) {
var coords = canvasPos(evt);
var color = rgb2hex(colorAt(coords));
createColorDiv(color);
changeColor(color);
}, true);
},
true,
);
canvas.addEventListener("mouseleave", function(){
canvas.addEventListener(
"mouseleave",
function () {
previewdiv.style.backgroundColor = curcolor;
}, true);
},
true,
);
}
//Put a color picker on every input[type=color] if the browser doesn't support this input type
//and on every input with the class canvascolor
var pickers = document.querySelectorAll("input.canvascolor, input[type=color]");
for (var i=0;i <pickers.length; i++) {
var pickers = document.querySelectorAll(
"input.canvascolor, input[type=color]",
);
for (var i = 0; i < pickers.length; i++) {
var input = pickers.item(i);
//If the browser supports native color picker and the user didn't
//explicitly added canvascolor to the element, we do not add a custom color picker
if (input.type !== "color" ||
input.className.split(" ").indexOf("canvascolor") !== -1) {
if (
input.type !== "color" ||
input.className.split(" ").indexOf("canvascolor") !== -1
) {
canvascolor(input);
}
}
return canvascolor;
}());
})();

View file

@ -9,7 +9,7 @@ function showRecentBoards() {
var list = document.createElement("ul");
recentBoards.forEach(function(name) {
recentBoards.forEach(function (name) {
var listItem = document.createElement("li");
var link = document.createElement("a");
link.setAttribute("href", `/boards/${encodeURIComponent(name)}`);

View file

@ -24,10 +24,11 @@
* @licend
*/
if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototype.transformedBBoxContains) {
[pointInTransformedBBox,
transformedBBoxIntersects] = (function () {
if (
!SVGGraphicsElement.prototype.transformedBBox ||
!SVGGraphicsElement.prototype.transformedBBoxContains
) {
[pointInTransformedBBox, transformedBBoxIntersects] = (function () {
var get_transform_matrix = function (elem) {
// Returns the first translate or transform matrix or makes one
var transform = null;
@ -41,84 +42,82 @@ if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototy
}
}
if (transform == null) {
transform = elem.transform.baseVal.createSVGTransformFromMatrix(Tools.svg.createSVGMatrix());
transform = elem.transform.baseVal.createSVGTransformFromMatrix(
Tools.svg.createSVGMatrix(),
);
elem.transform.baseVal.appendItem(transform);
}
return transform.matrix;
}
};
var transformRelative = function (m,t) {
return [
m.a*t[0]+m.c*t[1],
m.b*t[0]+m.d*t[1]
]
}
var transformRelative = function (m, t) {
return [m.a * t[0] + m.c * t[1], m.b * t[0] + m.d * t[1]];
};
var transformAbsolute = function (m,t) {
return [
m.a*t[0]+m.c*t[1]+m.e,
m.b*t[0]+m.d*t[1]+m.f
]
}
var transformAbsolute = function (m, t) {
return [m.a * t[0] + m.c * t[1] + m.e, m.b * t[0] + m.d * t[1] + m.f];
};
SVGGraphicsElement.prototype.transformedBBox = function (scale=1) {
SVGGraphicsElement.prototype.transformedBBox = function (scale = 1) {
bbox = this.getBBox();
tmatrix = get_transform_matrix(this);
tmatrix.e /= scale;
tmatrix.f /= scale;
return {
r: transformAbsolute(tmatrix,[bbox.x/scale,bbox.y/scale]),
a: transformRelative(tmatrix,[bbox.width/scale,0]),
b: transformRelative(tmatrix,[0,bbox.height/scale])
}
}
r: transformAbsolute(tmatrix, [bbox.x / scale, bbox.y / scale]),
a: transformRelative(tmatrix, [bbox.width / scale, 0]),
b: transformRelative(tmatrix, [0, bbox.height / scale]),
};
};
SVGSVGElement.prototype.transformedBBox = function (scale=1) {
SVGSVGElement.prototype.transformedBBox = function (scale = 1) {
bbox = {
x: this.x.baseVal.value,
y: this.y.baseVal.value,
width: this.width.baseVal.value,
height: this.height.baseVal.value
height: this.height.baseVal.value,
};
tmatrix = get_transform_matrix(this);
tmatrix.e /= scale;
tmatrix.f /= scale;
return {
r: transformAbsolute(tmatrix,[bbox.x/scale,bbox.y/scale]),
a: transformRelative(tmatrix,[bbox.width/scale,0]),
b: transformRelative(tmatrix,[0,bbox.height/scale])
}
}
r: transformAbsolute(tmatrix, [bbox.x / scale, bbox.y / scale]),
a: transformRelative(tmatrix, [bbox.width / scale, 0]),
b: transformRelative(tmatrix, [0, bbox.height / scale]),
};
};
var pointInTransformedBBox = function ([x,y],{r,a,b}) {
var d = [x-r[0],y-r[1]];
var idet = (a[0]*b[1]-a[1]*b[0]);
var c1 = (d[0]*b[1]-d[1]*b[0]) / idet;
var c2 = (d[1]*a[0]-d[0]*a[1]) / idet;
return (c1>=0 && c1<=1 && c2>=0 && c2<=1)
}
var pointInTransformedBBox = function ([x, y], { r, a, b }) {
var d = [x - r[0], y - r[1]];
var idet = a[0] * b[1] - a[1] * b[0];
var c1 = (d[0] * b[1] - d[1] * b[0]) / idet;
var c2 = (d[1] * a[0] - d[0] * a[1]) / idet;
return c1 >= 0 && c1 <= 1 && c2 >= 0 && c2 <= 1;
};
SVGGraphicsElement.prototype.transformedBBoxContains = function (x,y) {
return pointInTransformedBBox([x, y], this.transformedBBox())
}
SVGGraphicsElement.prototype.transformedBBoxContains = function (x, y) {
return pointInTransformedBBox([x, y], this.transformedBBox());
};
function transformedBBoxIntersects(bbox_a,bbox_b) {
function transformedBBoxIntersects(bbox_a, bbox_b) {
var corners = [
bbox_b.r,
[bbox_b.r[0] + bbox_b.a[0], bbox_b.r[1] + bbox_b.a[1]],
[bbox_b.r[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.b[1]],
[bbox_b.r[0] + bbox_b.a[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.a[1] + bbox_b.b[1]]
]
return corners.every(function(corner) {
[
bbox_b.r[0] + bbox_b.a[0] + bbox_b.b[0],
bbox_b.r[1] + bbox_b.a[1] + bbox_b.b[1],
],
];
return corners.every(function (corner) {
return pointInTransformedBBox(corner, bbox_a);
})
});
}
SVGGraphicsElement.prototype.transformedBBoxIntersects= function (bbox) {
return transformedBBoxIntersects(this.transformedBBox(),bbox)
}
SVGGraphicsElement.prototype.transformedBBoxIntersects = function (bbox) {
return transformedBBoxIntersects(this.transformedBBox(), bbox);
};
return [pointInTransformedBBox,
transformedBBoxIntersects]
return [pointInTransformedBBox, transformedBBoxIntersects];
})();
}

View file

@ -25,9 +25,8 @@
*/
Minitpl = (function () {
function Minitpl(elem, data) {
this.elem = (typeof (elem) === "string") ? document.querySelector(elem) : elem;
this.elem = typeof elem === "string" ? document.querySelector(elem) : elem;
if (!elem) {
throw "Invalid element!";
}
@ -36,7 +35,7 @@ Minitpl = (function () {
}
function transform(element, transformer) {
if (typeof (transformer) === "function") {
if (typeof transformer === "function") {
transformer(element);
} else {
element.textContent = transformer;
@ -45,7 +44,7 @@ Minitpl = (function () {
Minitpl.prototype.add = function (data) {
var newElem = this.elem.cloneNode(true);
if (typeof (data) === "object") {
if (typeof data === "object") {
for (var key in data) {
var matches = newElem.querySelectorAll(key);
for (var i = 0; i < matches.length; i++) {
@ -57,8 +56,7 @@ Minitpl = (function () {
}
this.parent.appendChild(newElem);
return newElem;
}
};
return Minitpl;
}());
})();

View file

@ -1,4 +1,3 @@
// @info
// Polyfill for SVG getPathData() and setPathData() methods. Based on:
// - SVGPathSeg polyfill by Philip Rogers (MIT License)
@ -11,11 +10,32 @@
// Jarosław Foksa
// @license
// MIT License
if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathData) {
if (
!SVGPathElement.prototype.getPathData ||
!SVGPathElement.prototype.setPathData
) {
(function () {
var commandsMap = {
"Z": "Z", "M": "M", "L": "L", "C": "C", "Q": "Q", "A": "A", "H": "H", "V": "V", "S": "S", "T": "T",
"z": "Z", "m": "m", "l": "l", "c": "c", "q": "q", "a": "a", "h": "h", "v": "v", "s": "s", "t": "t"
Z: "Z",
M: "M",
L: "L",
C: "C",
Q: "Q",
A: "A",
H: "H",
V: "V",
S: "S",
T: "T",
z: "Z",
m: "m",
l: "l",
c: "c",
q: "q",
a: "a",
h: "h",
v: "v",
s: "s",
t: "t",
};
var Source = function (string) {
@ -41,27 +61,27 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
// Check for remaining coordinates in the current command.
if (
(char === "+" || char === "-" || char === "." || (char >= "0" && char <= "9")) && this._prevCommand !== "Z"
(char === "+" ||
char === "-" ||
char === "." ||
(char >= "0" && char <= "9")) &&
this._prevCommand !== "Z"
) {
if (this._prevCommand === "M") {
command = "L";
}
else if (this._prevCommand === "m") {
} else if (this._prevCommand === "m") {
command = "l";
}
else {
} else {
command = this._prevCommand;
}
}
else {
} else {
command = null;
}
if (command === null) {
return null;
}
}
else {
} else {
this._currentIndex += 1;
}
@ -72,24 +92,25 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
if (cmd === "H" || cmd === "V") {
values = [this._parseNumber()];
}
else if (cmd === "M" || cmd === "L" || cmd === "T") {
} else if (cmd === "M" || cmd === "L" || cmd === "T") {
values = [this._parseNumber(), this._parseNumber()];
}
else if (cmd === "S" || cmd === "Q") {
values = [this._parseNumber(), this._parseNumber(), this._parseNumber(), this._parseNumber()];
}
else if (cmd === "C") {
} else if (cmd === "S" || cmd === "Q") {
values = [
this._parseNumber(),
this._parseNumber(),
this._parseNumber(),
this._parseNumber(),
];
} else if (cmd === "C") {
values = [
this._parseNumber(),
this._parseNumber(),
this._parseNumber(),
this._parseNumber(),
this._parseNumber(),
this._parseNumber()
this._parseNumber(),
];
}
else if (cmd === "A") {
} else if (cmd === "A") {
values = [
this._parseNumber(),
this._parseNumber(),
@ -97,10 +118,9 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
this._parseArcFlag(),
this._parseArcFlag(),
this._parseNumber(),
this._parseNumber()
this._parseNumber(),
];
}
else if (cmd === "Z") {
} else if (cmd === "Z") {
this._skipOptionalSpaces();
values = [];
}
@ -108,8 +128,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
if (values === null || values.indexOf(null) >= 0) {
// Unknown command or known command with invalid values
return null;
}
else {
} else {
return { type: command, values: values };
}
},
@ -136,7 +155,14 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
_isCurrentSpace: function () {
var char = this._string[this._currentIndex];
return char <= " " && (char === " " || char === "\n" || char === "\t" || char === "\r" || char === "\f");
return (
char <= " " &&
(char === " " ||
char === "\n" ||
char === "\t" ||
char === "\r" ||
char === "\f")
);
},
_skipOptionalSpaces: function () {
@ -157,7 +183,10 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
}
if (this._skipOptionalSpaces()) {
if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ",") {
if (
this._currentIndex < this._endIndex &&
this._string[this._currentIndex] === ","
) {
this._currentIndex += 1;
this._skipOptionalSpaces();
}
@ -180,20 +209,24 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
this._skipOptionalSpaces();
// Read the sign.
if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "+") {
if (
this._currentIndex < this._endIndex &&
this._string[this._currentIndex] === "+"
) {
this._currentIndex += 1;
}
else if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "-") {
} else if (
this._currentIndex < this._endIndex &&
this._string[this._currentIndex] === "-"
) {
this._currentIndex += 1;
sign = -1;
}
if (
this._currentIndex === this._endIndex ||
(
(this._string[this._currentIndex] < "0" || this._string[this._currentIndex] > "9") &&
this._string[this._currentIndex] !== "."
)
((this._string[this._currentIndex] < "0" ||
this._string[this._currentIndex] > "9") &&
this._string[this._currentIndex] !== ".")
) {
// The first character of a number must be one of [0-9+-.].
return null;
@ -222,7 +255,10 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
}
// Read the decimals.
if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ".") {
if (
this._currentIndex < this._endIndex &&
this._string[this._currentIndex] === "."
) {
this._currentIndex += 1;
// There must be a least one digit following the .
@ -249,16 +285,17 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
if (
this._currentIndex !== startIndex &&
this._currentIndex + 1 < this._endIndex &&
(this._string[this._currentIndex] === "e" || this._string[this._currentIndex] === "E") &&
(this._string[this._currentIndex + 1] !== "x" && this._string[this._currentIndex + 1] !== "m")
(this._string[this._currentIndex] === "e" ||
this._string[this._currentIndex] === "E") &&
this._string[this._currentIndex + 1] !== "x" &&
this._string[this._currentIndex + 1] !== "m"
) {
this._currentIndex += 1;
// Read the sign of the exponent.
if (this._string[this._currentIndex] === "+") {
this._currentIndex += 1;
}
else if (this._string[this._currentIndex] === "-") {
} else if (this._string[this._currentIndex] === "-") {
this._currentIndex += 1;
expsign = -1;
}
@ -278,7 +315,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
this._string[this._currentIndex] <= "9"
) {
exponent *= 10;
exponent += (this._string[this._currentIndex] - "0");
exponent += this._string[this._currentIndex] - "0";
this._currentIndex += 1;
}
}
@ -311,17 +348,15 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
if (flagChar === "0") {
flag = 0;
}
else if (flagChar === "1") {
} else if (flagChar === "1") {
flag = 1;
}
else {
} else {
return null;
}
this._skipOptionalSpacesOrDelimiter();
return flag;
}
},
};
var parsePathDataString = function (string) {
@ -336,25 +371,37 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
if (pathSeg === null) {
break;
}
else {
} else {
pathData.push(pathSeg);
}
}
}
return pathData;
}
};
var setAttribute = SVGPathElement.prototype.setAttribute;
var removeAttribute = SVGPathElement.prototype.removeAttribute;
var $cachedPathData = window.Symbol ? Symbol() : "__cachedPathData";
var $cachedNormalizedPathData = window.Symbol ? Symbol() : "__cachedNormalizedPathData";
var $cachedNormalizedPathData = window.Symbol
? Symbol()
: "__cachedNormalizedPathData";
// @info
// Get an array of corresponding cubic bezier curve parameters for given arc curve paramters.
var arcToCubicCurves = function (x1, y1, x2, y2, r1, r2, angle, largeArcFlag, sweepFlag, _recursive) {
var arcToCubicCurves = function (
x1,
y1,
x2,
y2,
r1,
r2,
angle,
largeArcFlag,
sweepFlag,
_recursive,
) {
var degToRad = function (degrees) {
return (Math.PI * degrees) / 180;
};
@ -374,8 +421,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
f2 = _recursive[1];
cx = _recursive[2];
cy = _recursive[3];
}
else {
} else {
var p1 = rotate(x1, y1, -angleRad);
x1 = p1.x;
y1 = p1.y;
@ -398,8 +444,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
if (largeArcFlag === sweepFlag) {
sign = -1;
}
else {
} else {
sign = 1;
}
@ -411,8 +456,8 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
var k = sign * Math.sqrt(Math.abs(left / right));
cx = k * r1 * y / r2 + (x1 + x2) / 2;
cy = k * -r2 * x / r1 + (y1 + y2) / 2;
cx = (k * r1 * y) / r2 + (x1 + x2) / 2;
cy = (k * -r2 * x) / r1 + (y1 + y2) / 2;
f1 = Math.asin(parseFloat(((y1 - cy) / r2).toFixed(9)));
f2 = Math.asin(parseFloat(((y2 - cy) / r2).toFixed(9)));
@ -441,21 +486,31 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
var df = f2 - f1;
if (Math.abs(df) > (Math.PI * 120 / 180)) {
if (Math.abs(df) > (Math.PI * 120) / 180) {
var f2old = f2;
var x2old = x2;
var y2old = y2;
if (sweepFlag && f2 > f1) {
f2 = f1 + (Math.PI * 120 / 180) * (1);
}
else {
f2 = f1 + (Math.PI * 120 / 180) * (-1);
f2 = f1 + ((Math.PI * 120) / 180) * 1;
} else {
f2 = f1 + ((Math.PI * 120) / 180) * -1;
}
x2 = cx + r1 * Math.cos(f2);
y2 = cy + r2 * Math.sin(f2);
params = arcToCubicCurves(x2, y2, x2old, y2old, r1, r2, angle, 0, sweepFlag, [f2, f2old, cx, cy]);
params = arcToCubicCurves(
x2,
y2,
x2old,
y2old,
r1,
r2,
angle,
0,
sweepFlag,
[f2, f2old, cx, cy],
);
}
df = f2 - f1;
@ -465,8 +520,8 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
var c2 = Math.cos(f2);
var s2 = Math.sin(f2);
var t = Math.tan(df / 4);
var hx = 4 / 3 * r1 * t;
var hy = 4 / 3 * r2 * t;
var hx = (4 / 3) * r1 * t;
var hy = (4 / 3) * r2 * t;
var m1 = [x1, y1];
var m2 = [x1 + hx * s1, y1 - hy * c1];
@ -478,8 +533,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
if (_recursive) {
return [m2, m3, m4].concat(params);
}
else {
} else {
params = [m2, m3, m4].concat(params);
var curves = [];
@ -497,7 +551,10 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
var clonePathData = function (pathData) {
return pathData.map(function (seg) {
return { type: seg.type, values: Array.prototype.slice.call(seg.values) }
return {
type: seg.type,
values: Array.prototype.slice.call(seg.values),
};
});
};
@ -526,9 +583,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (type === "m") {
} else if (type === "m") {
var x = currentX + seg.values[0];
var y = currentY + seg.values[1];
@ -539,9 +594,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (type === "L") {
} else if (type === "L") {
var x = seg.values[0];
var y = seg.values[1];
@ -549,9 +602,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (type === "l") {
} else if (type === "l") {
var x = currentX + seg.values[0];
var y = currentY + seg.values[1];
@ -559,9 +610,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (type === "C") {
} else if (type === "C") {
var x1 = seg.values[0];
var y1 = seg.values[1];
var x2 = seg.values[2];
@ -569,13 +618,14 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
var x = seg.values[4];
var y = seg.values[5];
absolutizedPathData.push({ type: "C", values: [x1, y1, x2, y2, x, y] });
absolutizedPathData.push({
type: "C",
values: [x1, y1, x2, y2, x, y],
});
currentX = x;
currentY = y;
}
else if (type === "c") {
} else if (type === "c") {
var x1 = currentX + seg.values[0];
var y1 = currentY + seg.values[1];
var x2 = currentX + seg.values[2];
@ -583,13 +633,14 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
var x = currentX + seg.values[4];
var y = currentY + seg.values[5];
absolutizedPathData.push({ type: "C", values: [x1, y1, x2, y2, x, y] });
absolutizedPathData.push({
type: "C",
values: [x1, y1, x2, y2, x, y],
});
currentX = x;
currentY = y;
}
else if (type === "Q") {
} else if (type === "Q") {
var x1 = seg.values[0];
var y1 = seg.values[1];
var x = seg.values[2];
@ -599,9 +650,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (type === "q") {
} else if (type === "q") {
var x1 = currentX + seg.values[0];
var y1 = currentY + seg.values[1];
var x = currentX + seg.values[2];
@ -611,59 +660,61 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (type === "A") {
} else if (type === "A") {
var x = seg.values[5];
var y = seg.values[6];
absolutizedPathData.push({
type: "A",
values: [seg.values[0], seg.values[1], seg.values[2], seg.values[3], seg.values[4], x, y]
values: [
seg.values[0],
seg.values[1],
seg.values[2],
seg.values[3],
seg.values[4],
x,
y,
],
});
currentX = x;
currentY = y;
}
else if (type === "a") {
} else if (type === "a") {
var x = currentX + seg.values[5];
var y = currentY + seg.values[6];
absolutizedPathData.push({
type: "A",
values: [seg.values[0], seg.values[1], seg.values[2], seg.values[3], seg.values[4], x, y]
values: [
seg.values[0],
seg.values[1],
seg.values[2],
seg.values[3],
seg.values[4],
x,
y,
],
});
currentX = x;
currentY = y;
}
else if (type === "H") {
} else if (type === "H") {
var x = seg.values[0];
absolutizedPathData.push({ type: "H", values: [x] });
currentX = x;
}
else if (type === "h") {
} else if (type === "h") {
var x = currentX + seg.values[0];
absolutizedPathData.push({ type: "H", values: [x] });
currentX = x;
}
else if (type === "V") {
} else if (type === "V") {
var y = seg.values[0];
absolutizedPathData.push({ type: "V", values: [y] });
currentY = y;
}
else if (type === "v") {
} else if (type === "v") {
var y = currentY + seg.values[0];
absolutizedPathData.push({ type: "V", values: [y] });
currentY = y;
}
else if (type === "S") {
} else if (type === "S") {
var x2 = seg.values[0];
var y2 = seg.values[1];
var x = seg.values[2];
@ -673,9 +724,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (type === "s") {
} else if (type === "s") {
var x2 = currentX + seg.values[0];
var y2 = currentY + seg.values[1];
var x = currentX + seg.values[2];
@ -685,29 +734,23 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (type === "T") {
} else if (type === "T") {
var x = seg.values[0];
var y = seg.values[1]
var y = seg.values[1];
absolutizedPathData.push({ type: "T", values: [x, y] });
currentX = x;
currentY = y;
}
else if (type === "t") {
} else if (type === "t") {
var x = currentX + seg.values[0];
var y = currentY + seg.values[1]
var y = currentY + seg.values[1];
absolutizedPathData.push({ type: "T", values: [x, y] });
currentX = x;
currentY = y;
}
else if (type === "Z" || type === "z") {
} else if (type === "Z" || type === "z") {
absolutizedPathData.push({ type: "Z", values: [] });
currentX = subpathX;
@ -746,9 +789,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (seg.type === "C") {
} else if (seg.type === "C") {
var x1 = seg.values[0];
var y1 = seg.values[1];
var x2 = seg.values[2];
@ -763,9 +804,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (seg.type === "L") {
} else if (seg.type === "L") {
var x = seg.values[0];
var y = seg.values[1];
@ -773,25 +812,19 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (seg.type === "H") {
} else if (seg.type === "H") {
var x = seg.values[0];
reducedPathData.push({ type: "L", values: [x, currentY] });
currentX = x;
}
else if (seg.type === "V") {
} else if (seg.type === "V") {
var y = seg.values[0];
reducedPathData.push({ type: "L", values: [currentX, y] });
currentY = y;
}
else if (seg.type === "S") {
} else if (seg.type === "S") {
var x2 = seg.values[0];
var y2 = seg.values[1];
var x = seg.values[2];
@ -802,8 +835,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
if (lastType === "C" || lastType === "S") {
cx1 = currentX + (currentX - lastControlX);
cy1 = currentY + (currentY - lastControlY);
}
else {
} else {
cx1 = currentX;
cy1 = currentY;
}
@ -815,9 +847,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentX = x;
currentY = y;
}
else if (seg.type === "T") {
} else if (seg.type === "T") {
var x = seg.values[0];
var y = seg.values[1];
@ -826,47 +856,48 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
if (lastType === "Q" || lastType === "T") {
x1 = currentX + (currentX - lastControlX);
y1 = currentY + (currentY - lastControlY);
}
else {
} else {
x1 = currentX;
y1 = currentY;
}
var cx1 = currentX + 2 * (x1 - currentX) / 3;
var cy1 = currentY + 2 * (y1 - currentY) / 3;
var cx2 = x + 2 * (x1 - x) / 3;
var cy2 = y + 2 * (y1 - y) / 3;
var cx1 = currentX + (2 * (x1 - currentX)) / 3;
var cy1 = currentY + (2 * (y1 - currentY)) / 3;
var cx2 = x + (2 * (x1 - x)) / 3;
var cy2 = y + (2 * (y1 - y)) / 3;
reducedPathData.push({ type: "C", values: [cx1, cy1, cx2, cy2, x, y] });
reducedPathData.push({
type: "C",
values: [cx1, cy1, cx2, cy2, x, y],
});
lastControlX = x1;
lastControlY = y1;
currentX = x;
currentY = y;
}
else if (seg.type === "Q") {
} else if (seg.type === "Q") {
var x1 = seg.values[0];
var y1 = seg.values[1];
var x = seg.values[2];
var y = seg.values[3];
var cx1 = currentX + 2 * (x1 - currentX) / 3;
var cy1 = currentY + 2 * (y1 - currentY) / 3;
var cx2 = x + 2 * (x1 - x) / 3;
var cy2 = y + 2 * (y1 - y) / 3;
var cx1 = currentX + (2 * (x1 - currentX)) / 3;
var cy1 = currentY + (2 * (y1 - currentY)) / 3;
var cx2 = x + (2 * (x1 - x)) / 3;
var cy2 = y + (2 * (y1 - y)) / 3;
reducedPathData.push({ type: "C", values: [cx1, cy1, cx2, cy2, x, y] });
reducedPathData.push({
type: "C",
values: [cx1, cy1, cx2, cy2, x, y],
});
lastControlX = x1;
lastControlY = y1;
currentX = x;
currentY = y;
}
else if (seg.type === "A") {
} else if (seg.type === "A") {
var r1 = Math.abs(seg.values[0]);
var r2 = Math.abs(seg.values[1]);
var angle = seg.values[2];
@ -876,14 +907,26 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
var y = seg.values[6];
if (r1 === 0 || r2 === 0) {
reducedPathData.push({ type: "C", values: [currentX, currentY, x, y, x, y] });
reducedPathData.push({
type: "C",
values: [currentX, currentY, x, y, x, y],
});
currentX = x;
currentY = y;
}
else {
} else {
if (currentX !== x || currentY !== y) {
var curves = arcToCubicCurves(currentX, currentY, x, y, r1, r2, angle, largeArcFlag, sweepFlag);
var curves = arcToCubicCurves(
currentX,
currentY,
x,
y,
r1,
r2,
angle,
largeArcFlag,
sweepFlag,
);
curves.forEach(function (curve) {
reducedPathData.push({ type: "C", values: curve });
@ -893,9 +936,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
currentY = y;
}
}
}
else if (seg.type === "Z") {
} else if (seg.type === "Z") {
reducedPathData.push(seg);
currentX = subpathX;
@ -930,14 +971,12 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
if (options && options.normalize) {
if (this[$cachedNormalizedPathData]) {
return clonePathData(this[$cachedNormalizedPathData]);
}
else {
} else {
var pathData;
if (this[$cachedPathData]) {
pathData = clonePathData(this[$cachedPathData]);
}
else {
} else {
pathData = parsePathDataString(this.getAttribute("d") || "");
this[$cachedPathData] = clonePathData(pathData);
}
@ -946,12 +985,10 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
this[$cachedNormalizedPathData] = clonePathData(normalizedPathData);
return normalizedPathData;
}
}
else {
} else {
if (this[$cachedPathData]) {
return clonePathData(this[$cachedPathData]);
}
else {
} else {
var pathData = parsePathDataString(this.getAttribute("d") || "");
this[$cachedPathData] = clonePathData(pathData);
return pathData;
@ -964,12 +1001,10 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
if (isIE) {
// @bugfix https://github.com/mbostock/d3/issues/1737
this.setAttribute("d", "");
}
else {
} else {
this.removeAttribute("d");
}
}
else {
} else {
var d = "";
for (var i = 0, l = pathData.length; i < l; i += 1) {
@ -995,8 +1030,12 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
var y = this.y.baseVal.value;
var width = this.width.baseVal.value;
var height = this.height.baseVal.value;
var rx = this.hasAttribute("rx") ? this.rx.baseVal.value : this.ry.baseVal.value;
var ry = this.hasAttribute("ry") ? this.ry.baseVal.value : this.rx.baseVal.value;
var rx = this.hasAttribute("rx")
? this.rx.baseVal.value
: this.ry.baseVal.value;
var ry = this.hasAttribute("ry")
? this.ry.baseVal.value
: this.rx.baseVal.value;
if (rx > width / 2) {
rx = width / 2;
@ -1016,12 +1055,14 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
{ type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
{ type: "V", values: [y + ry] },
{ type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
{ type: "Z", values: [] }
{ type: "Z", values: [] },
];
// Get rid of redundant "A" segs when either rx or ry is 0
pathData = pathData.filter(function (s) {
return s.type === "A" && (s.values[0] === 0 || s.values[1] === 0) ? false : true;
return s.type === "A" && (s.values[0] === 0 || s.values[1] === 0)
? false
: true;
});
if (options && options.normalize === true) {
@ -1042,7 +1083,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
{ type: "A", values: [r, r, 0, 0, 1, cx - r, cy] },
{ type: "A", values: [r, r, 0, 0, 1, cx, cy - r] },
{ type: "A", values: [r, r, 0, 0, 1, cx + r, cy] },
{ type: "Z", values: [] }
{ type: "Z", values: [] },
];
if (options && options.normalize === true) {
@ -1064,7 +1105,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
{ type: "A", values: [rx, ry, 0, 0, 1, cx - rx, cy] },
{ type: "A", values: [rx, ry, 0, 0, 1, cx, cy - ry] },
{ type: "A", values: [rx, ry, 0, 0, 1, cx + rx, cy] },
{ type: "Z", values: [] }
{ type: "Z", values: [] },
];
if (options && options.normalize === true) {
@ -1077,7 +1118,7 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
SVGLineElement.prototype.getPathData = function () {
return [
{ type: "M", values: [this.x1.baseVal.value, this.y1.baseVal.value] },
{ type: "L", values: [this.x2.baseVal.value, this.y2.baseVal.value] }
{ type: "L", values: [this.x2.baseVal.value, this.y2.baseVal.value] },
];
};
@ -1088,8 +1129,8 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
var point = this.points.getItem(i);
pathData.push({
type: (i === 0 ? "M" : "L"),
values: [point.x, point.y]
type: i === 0 ? "M" : "L",
values: [point.x, point.y],
});
}
@ -1103,14 +1144,14 @@ if (!SVGPathElement.prototype.getPathData || !SVGPathElement.prototype.setPathDa
var point = this.points.getItem(i);
pathData.push({
type: (i === 0 ? "M" : "L"),
values: [point.x, point.y]
type: i === 0 ? "M" : "L",
values: [point.x, point.y],
});
}
pathData.push({
type: "Z",
values: []
values: [],
});
return pathData;

View file

@ -24,13 +24,14 @@
* @licend
*/
(function clear() { //Code isolation
(function clear() {
//Code isolation
function clearBoard() {
var msg = {
"type": "clear",
"id": "",
"token": Tools.token
type: "clear",
id: "",
token: Tools.token,
};
Tools.drawAndSend(msg, Tools.list["Clear"]);
}
@ -39,15 +40,15 @@
Tools.drawingArea.innerHTML = "";
}
Tools.add({ //The new tool
"name": "Clear",
"shortcut": "c",
"listeners": {},
"icon": "tools/clear/clear.svg",
"oneTouch": true,
"onstart": clearBoard,
"draw": draw,
"mouseCursor": "crosshair",
Tools.add({
//The new tool
name: "Clear",
shortcut: "c",
listeners: {},
icon: "tools/clear/clear.svg",
oneTouch: true,
onstart: clearBoard,
draw: draw,
mouseCursor: "crosshair",
});
})(); //End of code isolation

View file

@ -24,10 +24,14 @@
* @licend
*/
(function () { // Code isolation
(function () {
// Code isolation
// Allocate half of the maximum server updates to cursor updates
var MIN_CURSOR_UPDATES_INTERVAL_MS = Tools.server_config.MAX_EMIT_COUNT_PERIOD / Tools.server_config.MAX_EMIT_COUNT * 2;
var MIN_CURSOR_UPDATES_INTERVAL_MS =
(Tools.server_config.MAX_EMIT_COUNT_PERIOD /
Tools.server_config.MAX_EMIT_COUNT) *
2;
var CURSOR_DELETE_AFTER_MS = 1000 * 5;
@ -35,16 +39,20 @@
var sending = true;
var cursorTool = {
"name": "Cursor",
"listeners": {
"press": function () { sending = false },
"move": handleMarker,
"release": function () { sending = true },
name: "Cursor",
listeners: {
press: function () {
sending = false;
},
"onSizeChange": onSizeChange,
"draw": draw,
"mouseCursor": "crosshair",
"icon": "tools/pencil/icon.svg",
move: handleMarker,
release: function () {
sending = true;
},
},
onSizeChange: onSizeChange,
draw: draw,
mouseCursor: "crosshair",
icon: "tools/pencil/icon.svg",
};
Tools.register(cursorTool);
Tools.addToolListeners(cursorTool);
@ -74,8 +82,10 @@
function updateMarker() {
if (!Tools.showMarker || !Tools.showMyCursor) return;
var cur_time = Date.now();
if (cur_time - lastCursorUpdate > MIN_CURSOR_UPDATES_INTERVAL_MS &&
(sending || Tools.curTool.showMarker)) {
if (
cur_time - lastCursorUpdate > MIN_CURSOR_UPDATES_INTERVAL_MS &&
(sending || Tools.curTool.showMarker)
) {
Tools.drawAndSend(message, cursorTool);
lastCursorUpdate = cur_time;
} else {
@ -86,7 +96,10 @@
var cursorsElem = Tools.svg.getElementById("cursors");
function createCursor(id) {
var cursor = document.createElementNS("http://www.w3.org/2000/svg", "circle");
var cursor = document.createElementNS(
"http://www.w3.org/2000/svg",
"circle",
);
cursor.setAttributeNS(null, "class", "opcursor");
cursor.setAttributeNS(null, "id", id);
cursor.setAttributeNS(null, "cx", 0);
@ -104,9 +117,15 @@
}
function draw(message) {
var cursor = getCursor("cursor-" + (message.socket || 'me'));
cursor.style.transform = "translate(" + message.x + "px, " + message.y + "px)";
if (Tools.isIE) cursor.setAttributeNS(null, "transform", "translate(" + message.x + " " + message.y + ")");
var cursor = getCursor("cursor-" + (message.socket || "me"));
cursor.style.transform =
"translate(" + message.x + "px, " + message.y + "px)";
if (Tools.isIE)
cursor.setAttributeNS(
null,
"transform",
"translate(" + message.x + " " + message.y + ")",
);
cursor.setAttributeNS(null, "fill", message.color);
cursor.setAttributeNS(null, "r", message.size / 2);
}

View file

@ -24,7 +24,8 @@
* @licend
*/
(function download() { //Code isolation
(function download() {
//Code isolation
function downloadSVGFile() {
var canvasCopy = Tools.svg.cloneNode(true);
@ -34,8 +35,11 @@
// Copy the stylesheets from the whiteboard to the exported SVG
styleNode.innerHTML = Array.from(document.styleSheets)
.filter(function (stylesheet) {
if (stylesheet.href && (stylesheet.href.match(/boards\/tools\/.*\.css/)
|| stylesheet.href.match(/board\.css/))) {
if (
stylesheet.href &&
(stylesheet.href.match(/boards\/tools\/.*\.css/) ||
stylesheet.href.match(/board\.css/))
) {
// This is a Stylesheet from a Tool or the Board itself, so we should include it
return true;
}
@ -43,25 +47,29 @@
return false;
})
.map(function (stylesheet) {
return Array.from(stylesheet.cssRules)
.map(function (rule) { return rule.cssText })
}).join("\n")
return Array.from(stylesheet.cssRules).map(function (rule) {
return rule.cssText;
});
})
.join("\n");
canvasCopy.appendChild(styleNode);
var outerHTML = canvasCopy.outerHTML || new XMLSerializer().serializeToString(canvasCopy);
var blob = new Blob([outerHTML], { type: 'image/svg+xml;charset=utf-8' });
var outerHTML =
canvasCopy.outerHTML || new XMLSerializer().serializeToString(canvasCopy);
var blob = new Blob([outerHTML], { type: "image/svg+xml;charset=utf-8" });
downloadContent(blob, Tools.boardName + ".svg");
}
function downloadContent(blob, filename) {
if (window.navigator.msSaveBlob) { // Internet Explorer
if (window.navigator.msSaveBlob) {
// Internet Explorer
window.navigator.msSaveBlob(blob, filename);
} else {
const url = URL.createObjectURL(blob);
var element = document.createElement('a');
element.setAttribute('href', url);
element.setAttribute('download', filename);
element.style.display = 'none';
var element = document.createElement("a");
element.setAttribute("href", url);
element.setAttribute("download", filename);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
@ -69,14 +77,14 @@
}
}
Tools.add({ //The new tool
"name": "Download",
"shortcut": "d",
"listeners": {},
"icon": "tools/download/download.svg",
"oneTouch": true,
"onstart": downloadSVGFile,
"mouseCursor": "crosshair",
Tools.add({
//The new tool
name: "Download",
shortcut: "d",
listeners: {},
icon: "tools/download/download.svg",
oneTouch: true,
onstart: downloadSVGFile,
mouseCursor: "crosshair",
});
})(); //End of code isolation

View file

@ -24,35 +24,36 @@
* @licend
*/
(function () { //Code isolation
var curUpdate = { //The data of the message that will be sent for every new point
'type': 'update',
'id': "",
'x': 0,
'y': 0,
'x2': 0,
'y2': 0
(function () {
//Code isolation
var curUpdate = {
//The data of the message that will be sent for every new point
type: "update",
id: "",
x: 0,
y: 0,
x2: 0,
y2: 0,
},
lastPos = { x: 0, y: 0 },
lastTime = performance.now(); //The time at which the last point was drawn
function start(x, y, evt) {
//Prevent the press from being interpreted by the browser
evt.preventDefault();
curUpdate.id = Tools.generateUID("e"); //"e" for ellipse
Tools.drawAndSend({
'type': 'ellipse',
'id': curUpdate.id,
'color': Tools.getColor(),
'size': Tools.getSize(),
'opacity': Tools.getOpacity(),
'x': x,
'y': y,
'x2': x,
'y2': y
type: "ellipse",
id: curUpdate.id,
color: Tools.getColor(),
size: Tools.getSize(),
opacity: Tools.getOpacity(),
x: x,
y: y,
x2: x,
y2: y,
});
curUpdate.id = curUpdate.id;
@ -74,14 +75,16 @@
function doUpdate(force) {
if (!curUpdate.id) return; // Not currently drawing
if (drawingCircle()) {
var x0 = curUpdate['x'], y0 = curUpdate['y'];
var deltaX = lastPos.x - x0, deltaY = lastPos.y - y0;
var x0 = curUpdate["x"],
y0 = curUpdate["y"];
var deltaX = lastPos.x - x0,
deltaY = lastPos.y - y0;
var diameter = Math.max(Math.abs(deltaX), Math.abs(deltaY));
curUpdate['x2'] = x0 + (deltaX > 0 ? diameter : -diameter);
curUpdate['y2'] = y0 + (deltaY > 0 ? diameter : -diameter);
curUpdate["x2"] = x0 + (deltaX > 0 ? diameter : -diameter);
curUpdate["y2"] = y0 + (deltaY > 0 ? diameter : -diameter);
} else {
curUpdate['x2'] = lastPos.x;
curUpdate['y2'] = lastPos.y;
curUpdate["x2"] = lastPos.x;
curUpdate["y2"] = lastPos.y;
}
if (performance.now() - lastTime > 70 || force) {
@ -106,13 +109,17 @@
createShape(data);
break;
case "update":
var shape = svg.getElementById(data['id']);
var shape = svg.getElementById(data["id"]);
if (!shape) {
console.error("Ellipse: Hmmm... I received an update for a shape that has not been created (%s).", data['id']);
createShape({ //create a new shape in order not to loose the points
"id": data['id'],
"x": data['x2'],
"y": data['y2']
console.error(
"Ellipse: Hmmm... I received an update for a shape that has not been created (%s).",
data["id"],
);
createShape({
//create a new shape in order not to loose the points
id: data["id"],
x: data["x2"],
y: data["y2"],
});
}
updateShape(shape, data);
@ -126,47 +133,51 @@
var svg = Tools.svg;
function createShape(data) {
//Creates a new shape on the canvas, or update a shape that already exists with new information
var shape = svg.getElementById(data.id) || Tools.createSVGElement("ellipse");
var shape =
svg.getElementById(data.id) || Tools.createSVGElement("ellipse");
updateShape(shape, data);
shape.id = data.id;
//If some data is not provided, choose default value. The shape may be updated later
shape.setAttribute("stroke", data.color || "black");
shape.setAttribute("stroke-width", data.size || 10);
shape.setAttribute("opacity", Math.max(0.1, Math.min(1, data.opacity)) || 1);
shape.setAttribute(
"opacity",
Math.max(0.1, Math.min(1, data.opacity)) || 1,
);
Tools.drawingArea.appendChild(shape);
return shape;
}
function updateShape(shape, data) {
shape.cx.baseVal.value = Math.round((data['x2'] + data['x']) / 2);
shape.cy.baseVal.value = Math.round((data['y2'] + data['y']) / 2);
shape.rx.baseVal.value = Math.abs(data['x2'] - data['x']) / 2;
shape.ry.baseVal.value = Math.abs(data['y2'] - data['y']) / 2;
shape.cx.baseVal.value = Math.round((data["x2"] + data["x"]) / 2);
shape.cy.baseVal.value = Math.round((data["y2"] + data["y"]) / 2);
shape.rx.baseVal.value = Math.abs(data["x2"] - data["x"]) / 2;
shape.ry.baseVal.value = Math.abs(data["y2"] - data["y"]) / 2;
}
function drawingCircle() {
return circleTool.secondary.active;
}
var circleTool = { //The new tool
"name": "Ellipse",
"icon": "tools/ellipse/icon-ellipse.svg",
"secondary": {
"name": "Circle",
"icon": "tools/ellipse/icon-circle.svg",
"active": false,
"switch": doUpdate,
var circleTool = {
//The new tool
name: "Ellipse",
icon: "tools/ellipse/icon-ellipse.svg",
secondary: {
name: "Circle",
icon: "tools/ellipse/icon-circle.svg",
active: false,
switch: doUpdate,
},
"shortcut": "c",
"listeners": {
"press": start,
"move": move,
"release": stop,
shortcut: "c",
listeners: {
press: start,
move: move,
release: stop,
},
"draw": draw,
"mouseCursor": "crosshair",
"stylesheet": "tools/ellipse/ellipse.css"
draw: draw,
mouseCursor: "crosshair",
stylesheet: "tools/ellipse/ellipse.css",
};
Tools.add(circleTool);
})(); //End of code isolation

View file

@ -24,7 +24,8 @@
* @licend
*/
(function eraser() { //Code isolation
(function eraser() {
//Code isolation
var erasing = false;
@ -36,8 +37,8 @@
}
var msg = {
"type": "delete",
"id": ""
type: "delete",
id: "",
};
function inDrawingArea(elem) {
@ -53,7 +54,12 @@
var touch = evt.touches[0];
target = document.elementFromPoint(touch.clientX, touch.clientY);
}
if (erasing && target !== Tools.svg && target !== Tools.drawingArea && inDrawingArea(target)) {
if (
erasing &&
target !== Tools.svg &&
target !== Tools.drawingArea &&
inDrawingArea(target)
) {
msg.id = target.id;
Tools.drawAndSend(msg);
}
@ -69,7 +75,10 @@
//TODO: add the ability to erase only some points in a line
case "delete":
elem = svg.getElementById(data.id);
if (elem === null) console.error("Eraser: Tried to delete an element that does not exist.");
if (elem === null)
console.error(
"Eraser: Tried to delete an element that does not exist.",
);
else Tools.drawingArea.removeChild(elem);
break;
default:
@ -80,18 +89,18 @@
var svg = Tools.svg;
Tools.add({ //The new tool
"name": "Eraser",
"shortcut": "e",
"listeners": {
"press": startErasing,
"move": erase,
"release": stopErasing,
Tools.add({
//The new tool
name: "Eraser",
shortcut: "e",
listeners: {
press: startErasing,
move: erase,
release: stopErasing,
},
"draw": draw,
"icon": "tools/eraser/icon.svg",
"mouseCursor": "crosshair",
"showMarker": true,
draw: draw,
icon: "tools/eraser/icon.svg",
mouseCursor: "crosshair",
showMarker: true,
});
})(); //End of code isolation

View file

@ -24,7 +24,8 @@
* @licend
*/
(function grid() { //Code isolation
(function grid() {
//Code isolation
var index = 0; //grid off by default
var states = ["none", "url(#grid)", "url(#dots)"];
@ -41,34 +42,37 @@
id: "smallGrid",
width: "30",
height: "30",
patternUnits: "userSpaceOnUse"
patternUnits: "userSpaceOnUse",
});
smallGrid.appendChild(
Tools.createSVGElement("path", {
d: "M 30 0 L 0 0 0 30",
fill: "none",
stroke: "gray",
'stroke-width': "0.5"
})
"stroke-width": "0.5",
}),
);
// (outer) grid
var grid = Tools.createSVGElement("pattern", {
id: "grid",
width: "300",
height: "300",
patternUnits: "userSpaceOnUse"
patternUnits: "userSpaceOnUse",
});
grid.appendChild(Tools.createSVGElement("rect", {
grid.appendChild(
Tools.createSVGElement("rect", {
width: "300",
height: "300",
fill: "url(#smallGrid)"
}));
fill: "url(#smallGrid)",
}),
);
grid.appendChild(
Tools.createSVGElement("path", {
d: "M 300 0 L 0 0 0 300",
fill: "none",
stroke: "gray", 'stroke-width': "1"
})
stroke: "gray",
"stroke-width": "1",
}),
);
// dots
var dots = Tools.createSVGElement("pattern", {
@ -77,14 +81,16 @@
height: "30",
x: "-10",
y: "-10",
patternUnits: "userSpaceOnUse"
patternUnits: "userSpaceOnUse",
});
dots.appendChild(Tools.createSVGElement("circle", {
dots.appendChild(
Tools.createSVGElement("circle", {
fill: "gray",
cx: "10",
cy: "10",
r: "2"
}));
r: "2",
}),
);
var defs = Tools.svg.getElementById("defs");
defs.appendChild(smallGrid);
@ -98,21 +104,22 @@
// create grid container
var gridContainer = Tools.createSVGElement("rect", {
id: "gridContainer",
width: "100%", height: "100%",
fill: states[index]
width: "100%",
height: "100%",
fill: states[index],
});
Tools.svg.insertBefore(gridContainer, Tools.drawingArea);
return gridContainer;
})();
Tools.add({ //The new tool
"name": "Grid",
"shortcut": "g",
"listeners": {},
"icon": "tools/grid/icon.svg",
"oneTouch": true,
"onstart": toggleGrid,
"mouseCursor": "crosshair",
Tools.add({
//The new tool
name: "Grid",
shortcut: "g",
listeners: {},
icon: "tools/grid/icon.svg",
oneTouch: true,
onstart: toggleGrid,
mouseCursor: "crosshair",
});
})(); //End of code isolation

View file

@ -24,12 +24,13 @@
* @licend
*/
(function hand() { //Code isolation
(function hand() {
//Code isolation
var selectorStates = {
pointing: 0,
selecting: 1,
transform: 2
}
transform: 2,
};
var selected = null;
var selected_els = [];
var selectionRect = createSelectorRect();
@ -40,7 +41,11 @@
var last_sent = 0;
var blockedSelectionButtons = Tools.server_config.BLOCKED_SELECTION_BUTTONS;
var selectionButtons = [
createButton("delete", "delete", 24, 24,
createButton(
"delete",
"delete",
24,
24,
function (me, bbox, s) {
me.width.baseVal.value = me.origWidth / s;
me.height.baseVal.value = me.origHeight / s;
@ -48,9 +53,14 @@
me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s;
me.style.display = "";
},
deleteSelection),
deleteSelection,
),
createButton("duplicate", "duplicate", 24, 24,
createButton(
"duplicate",
"duplicate",
24,
24,
function (me, bbox, s) {
me.width.baseVal.value = me.origWidth / s;
me.height.baseVal.value = me.origHeight / s;
@ -58,9 +68,14 @@
me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s;
me.style.display = "";
},
duplicateSelection),
duplicateSelection,
),
createButton("scaleHandle", "handle", 14, 14,
createButton(
"scaleHandle",
"handle",
14,
14,
function (me, bbox, s) {
me.width.baseVal.value = me.origWidth / s;
me.height.baseVal.value = me.origHeight / s;
@ -68,7 +83,8 @@
me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight / (2 * s);
me.style.display = "";
},
startScalingTransform)
startScalingTransform,
),
];
for (i in blockedSelectionButtons) {
@ -88,7 +104,7 @@
var parentMathematics = els.find(function (el) {
return el.getAttribute("class") === "MathElement";
});
if ((parentMathematics) && parentMathematics.tagName === "svg") {
if (parentMathematics && parentMathematics.tagName === "svg") {
target = parentMathematics;
}
return target || el;
@ -96,22 +112,22 @@
function deleteSelection() {
var msgs = selected_els.map(function (el) {
return ({
"type": "delete",
"id": el.id
});
return {
type: "delete",
id: el.id,
};
});
var data = {
_children: msgs
}
_children: msgs,
};
Tools.drawAndSend(data);
selected_els = [];
hideSelectionUI();
}
function duplicateSelection() {
if (!(selectorState == selectorStates.pointing)
|| (selected_els.length == 0)) return;
if (!(selectorState == selectorStates.pointing) || selected_els.length == 0)
return;
var msgs = [];
var newids = [];
for (var i = 0; i < selected_els.length; i++) {
@ -119,7 +135,7 @@
msgs[i] = {
type: "copy",
id: id,
newid: Tools.generateUID(id[0])
newid: Tools.generateUID(id[0]),
};
newids[i] = id;
}
@ -146,10 +162,18 @@
return shape;
}
function createButton(name, icon, width, height, drawCallback, clickCallback) {
function createButton(
name,
icon,
width,
height,
drawCallback,
clickCallback,
) {
var shape = Tools.createSVGElement("image", {
href: "tools/hand/" + icon + ".svg",
width: width, height: height
width: width,
height: height,
});
shape.style.display = "none";
shape.origWidth = width;
@ -164,9 +188,11 @@
var scale = getScale();
var selectionBBox = selectionRect.transformedBBox();
for (var i = 0; i < selectionButtons.length; i++) {
selectionButtons[i].drawCallback(selectionButtons[i],
selectionButtons[i].drawCallback(
selectionButtons[i],
selectionBBox,
scale);
scale,
);
}
}
@ -193,8 +219,12 @@
transform_elements = selected_els.map(function (el) {
var tmatrix = get_transform_matrix(el);
return {
a: tmatrix.a, b: tmatrix.b, c: tmatrix.c,
d: tmatrix.d, e: tmatrix.e, f: tmatrix.f
a: tmatrix.a,
b: tmatrix.b,
c: tmatrix.c,
d: tmatrix.d,
e: tmatrix.e,
f: tmatrix.f,
};
});
var tmatrix = get_transform_matrix(selectionRect);
@ -215,14 +245,20 @@
transform_elements = selected_els.map(function (el) {
var tmatrix = get_transform_matrix(el);
return {
a: tmatrix.a, b: tmatrix.b, c: tmatrix.c,
d: tmatrix.d, e: tmatrix.e, f: tmatrix.f
a: tmatrix.a,
b: tmatrix.b,
c: tmatrix.c,
d: tmatrix.d,
e: tmatrix.e,
f: tmatrix.f,
};
});
var tmatrix = get_transform_matrix(selectionRect);
selectionRectTransform = {
a: tmatrix.a, d: tmatrix.d,
e: tmatrix.e, f: tmatrix.f
a: tmatrix.a,
d: tmatrix.d,
e: tmatrix.e,
f: tmatrix.f,
};
currentTransform = scaleSelection;
}
@ -242,13 +278,14 @@
tmatrix.f = 0;
}
function calculateSelection() {
var selectionTBBox = selectionRect.transformedBBox();
var elements = Tools.drawingArea.children;
var selected = [];
for (var i = 0; i < elements.length; i++) {
if (transformedBBoxIntersects(selectionTBBox, elements[i].transformedBBox()))
if (
transformedBBoxIntersects(selectionTBBox, elements[i].transformedBBox())
)
selected.push(Tools.drawingArea.children[i]);
}
return selected;
@ -268,12 +305,12 @@
c: oldTransform.c,
d: oldTransform.d,
e: dx + oldTransform.e,
f: dy + oldTransform.f
}
f: dy + oldTransform.f,
},
};
})
});
var msg = {
_children: msgs
_children: msgs,
};
var tmatrix = get_transform_matrix(selectionRect);
tmatrix.e = dx + selectionRectTransform.x;
@ -288,18 +325,22 @@
}
function scaleSelection(x, y) {
var rx = (x - selected.x) / (selected.w);
var ry = (y - selected.y) / (selected.h);
var rx = (x - selected.x) / selected.w;
var ry = (y - selected.y) / selected.h;
var msgs = selected_els.map(function (el, i) {
var oldTransform = transform_elements[i];
var x = el.transformedBBox().r[0];
var y = el.transformedBBox().r[1];
var a = oldTransform.a * rx;
var d = oldTransform.d * ry;
var e = selected.x * (1 - rx) - x * a +
(x * oldTransform.a + oldTransform.e) * rx
var f = selected.y * (1 - ry) - y * d +
(y * oldTransform.d + oldTransform.f) * ry
var e =
selected.x * (1 - rx) -
x * a +
(x * oldTransform.a + oldTransform.e) * rx;
var f =
selected.y * (1 - ry) -
y * d +
(y * oldTransform.d + oldTransform.f) * ry;
return {
type: "update",
id: el.id,
@ -309,21 +350,23 @@
c: oldTransform.c,
d: d,
e: e,
f: f
}
f: f,
},
};
})
});
var msg = {
_children: msgs
_children: msgs,
};
var tmatrix = get_transform_matrix(selectionRect);
tmatrix.a = rx;
tmatrix.d = ry;
tmatrix.e = selectionRectTransform.e +
selectionRect.x.baseVal.value * (selectionRectTransform.a - rx)
tmatrix.f = selectionRectTransform.f +
selectionRect.y.baseVal.value * (selectionRectTransform.d - ry)
tmatrix.e =
selectionRectTransform.e +
selectionRect.x.baseVal.value * (selectionRectTransform.a - rx);
tmatrix.f =
selectionRectTransform.f +
selectionRect.y.baseVal.value * (selectionRectTransform.d - ry);
var now = performance.now();
if (now - last_sent > 70) {
last_sent = now;
@ -347,8 +390,12 @@
selectionRect.y.baseVal.value = bbox.r[1];
selectionRect.width.baseVal.value = bbox.a[0];
selectionRect.height.baseVal.value = bbox.b[1];
tmatrix.a = 1; tmatrix.b = 0; tmatrix.c = 0;
tmatrix.d = 1; tmatrix.e = 0; tmatrix.f = 0;
tmatrix.a = 1;
tmatrix.b = 0;
tmatrix.c = 0;
tmatrix.d = 1;
tmatrix.e = 0;
tmatrix.f = 0;
}
function get_transform_matrix(elem) {
@ -364,7 +411,9 @@
}
}
if (transform == null) {
transform = elem.transform.baseVal.createSVGTransformFromMatrix(Tools.svg.createSVGMatrix());
transform = elem.transform.baseVal.createSVGTransformFromMatrix(
Tools.svg.createSVGMatrix(),
);
elem.transform.baseVal.appendItem(transform);
}
return transform.matrix;
@ -373,15 +422,17 @@
function draw(data) {
if (data._children) {
batchCall(draw, data._children);
}
else {
} else {
switch (data.type) {
case "update":
var elem = Tools.svg.getElementById(data.id);
if (!elem) throw new Error("Mover: Tried to move an element that does not exist.");
if (!elem)
throw new Error(
"Mover: Tried to move an element that does not exist.",
);
var tmatrix = get_transform_matrix(elem);
for (i in data.transform) {
tmatrix[i] = data.transform[i]
tmatrix[i] = data.transform[i];
}
break;
case "copy":
@ -394,7 +445,10 @@
messageForTool(data);
break;
default:
throw new Error("Mover: 'move' instruction with unknown type. ", data);
throw new Error(
"Mover: 'move' instruction with unknown type. ",
data,
);
}
}
}
@ -408,7 +462,9 @@
}
if (button) {
button.clickCallback(x, y, evt);
} else if (pointInTransformedBBox([x, y], selectionRect.transformedBBox())) {
} else if (
pointInTransformedBBox([x, y], selectionRect.transformedBBox())
) {
hideSelectionButtons();
startMovingElements(x, y, evt);
} else if (Tools.drawingArea.contains(evt.target)) {
@ -427,8 +483,7 @@
if (selected_els.length == 0) {
hideSelectionUI();
}
} else if (selectorState == selectorStates.transform)
resetSelectionRect();
} else if (selectorState == selectorStates.transform) resetSelectionRect();
if (selected_els.length != 0) showSelectionButtons();
transform_elements = [];
selectorState = selectorStates.pointing;
@ -447,11 +502,12 @@
selected = {
x: document.documentElement.scrollLeft + evt.clientX,
y: document.documentElement.scrollTop + evt.clientY,
}
};
}
}
function moveHand(x, y, evt, isTouchEvent) {
if (selected && !isTouchEvent) { //Let the browser handle touch to scroll
if (selected && !isTouchEvent) {
//Let the browser handle touch to scroll
window.scrollTo(selected.x - evt.clientX, selected.y - evt.clientY);
}
}
@ -461,7 +517,6 @@
else clickSelector(x, y, evt, isTouchEvent);
}
function move(x, y, evt, isTouchEvent) {
if (!handTool.secondary.active) moveHand(x, y, evt, isTouchEvent);
else moveSelector(x, y, evt, isTouchEvent);
@ -474,14 +529,12 @@
}
function deleteShortcut(e) {
if (e.key == "Delete" &&
!e.target.matches("input[type=text], textarea"))
if (e.key == "Delete" && !e.target.matches("input[type=text], textarea"))
deleteSelection();
}
function duplicateShortcut(e) {
if (e.key == "d" &&
!e.target.matches("input[type=text], textarea"))
if (e.key == "d" && !e.target.matches("input[type=text], textarea"))
duplicateSelection();
}
@ -500,25 +553,26 @@
window.removeEventListener("keydown", duplicateShortcut);
}
var handTool = { //The new tool
"name": "Hand",
"shortcut": "h",
"listeners": {
"press": press,
"move": move,
"release": release,
var handTool = {
//The new tool
name: "Hand",
shortcut: "h",
listeners: {
press: press,
move: move,
release: release,
},
"onquit": onquit,
"secondary": {
"name": "Selector",
"icon": "tools/hand/selector.svg",
"active": false,
"switch": switchTool,
onquit: onquit,
secondary: {
name: "Selector",
icon: "tools/hand/selector.svg",
active: false,
switch: switchTool,
},
"draw": draw,
"icon": "tools/hand/hand.svg",
"mouseCursor": "move",
"showMarker": true,
draw: draw,
icon: "tools/hand/hand.svg",
mouseCursor: "move",
showMarker: true,
};
Tools.add(handTool);
Tools.change("Hand"); // Use the hand tool by default

View file

@ -24,33 +24,33 @@
* @licend
*/
(function () { //Code isolation
(function () {
//Code isolation
//Indicates the id of the line the user is currently drawing or an empty string while the user is not drawing
var curLine = null,
lastTime = performance.now(); //The time at which the last point was drawn
//The data of the message that will be sent for every update
function UpdateMessage(x, y) {
this.type = 'update';
this.type = "update";
this.id = curLine.id;
this.x2 = x;
this.y2 = y;
}
function startLine(x, y, evt) {
//Prevent the press from being interpreted by the browser
evt.preventDefault();
curLine = {
'type': 'straight',
'id': Tools.generateUID("s"), //"s" for straight line
'color': Tools.getColor(),
'size': Tools.getSize(),
'opacity': Tools.getOpacity(),
'x': x,
'y': y
}
type: "straight",
id: Tools.generateUID("s"), //"s" for straight line
color: Tools.getColor(),
size: Tools.getSize(),
opacity: Tools.getOpacity(),
x: x,
y: y,
};
Tools.drawAndSend(curLine);
}
@ -62,7 +62,7 @@
if (lineTool.secondary.active) {
var alpha = Math.atan2(y - curLine.y, x - curLine.x);
var d = Math.hypot(y - curLine.y, x - curLine.x);
var increment = 2 * Math.PI / 16;
var increment = (2 * Math.PI) / 16;
alpha = Math.round(alpha / increment) * increment;
x = curLine.x + d * Math.cos(alpha);
y = curLine.y + d * Math.sin(alpha);
@ -89,19 +89,26 @@
createLine(data);
break;
case "update":
var line = svg.getElementById(data['id']);
var line = svg.getElementById(data["id"]);
if (!line) {
console.error("Straight line: Hmmm... I received a point of a line that has not been created (%s).", data['id']);
createLine({ //create a new line in order not to loose the points
"id": data['id'],
"x": data['x2'],
"y": data['y2']
console.error(
"Straight line: Hmmm... I received a point of a line that has not been created (%s).",
data["id"],
);
createLine({
//create a new line in order not to loose the points
id: data["id"],
x: data["x2"],
y: data["y2"],
});
}
updateLine(line, data);
break;
default:
console.error("Straight Line: Draw instruction with unknown type. ", data);
console.error(
"Straight Line: Draw instruction with unknown type. ",
data,
);
break;
}
}
@ -109,42 +116,46 @@
var svg = Tools.svg;
function createLine(lineData) {
//Creates a new line on the canvas, or update a line that already exists with new information
var line = svg.getElementById(lineData.id) || Tools.createSVGElement("line");
var line =
svg.getElementById(lineData.id) || Tools.createSVGElement("line");
line.id = lineData.id;
line.x1.baseVal.value = lineData['x'];
line.y1.baseVal.value = lineData['y'];
line.x2.baseVal.value = lineData['x2'] || lineData['x'];
line.y2.baseVal.value = lineData['y2'] || lineData['y'];
line.x1.baseVal.value = lineData["x"];
line.y1.baseVal.value = lineData["y"];
line.x2.baseVal.value = lineData["x2"] || lineData["x"];
line.y2.baseVal.value = lineData["y2"] || lineData["y"];
//If some data is not provided, choose default value. The line may be updated later
line.setAttribute("stroke", lineData.color || "black");
line.setAttribute("stroke-width", lineData.size || 10);
line.setAttribute("opacity", Math.max(0.1, Math.min(1, lineData.opacity)) || 1);
line.setAttribute(
"opacity",
Math.max(0.1, Math.min(1, lineData.opacity)) || 1,
);
Tools.drawingArea.appendChild(line);
return line;
}
function updateLine(line, data) {
line.x2.baseVal.value = data['x2'];
line.y2.baseVal.value = data['y2'];
line.x2.baseVal.value = data["x2"];
line.y2.baseVal.value = data["y2"];
}
var lineTool = {
"name": "Straight line",
"shortcut": "l",
"listeners": {
"press": startLine,
"move": continueLine,
"release": stopLine,
name: "Straight line",
shortcut: "l",
listeners: {
press: startLine,
move: continueLine,
release: stopLine,
},
"secondary": {
"name": "Straight line",
"icon": "tools/line/icon-straight.svg",
"active": false,
secondary: {
name: "Straight line",
icon: "tools/line/icon-straight.svg",
active: false,
},
"draw": draw,
"mouseCursor": "crosshair",
"icon": "tools/line/icon.svg",
"stylesheet": "tools/line/line.css"
draw: draw,
mouseCursor: "crosshair",
icon: "tools/line/icon.svg",
stylesheet: "tools/line/line.css",
};
Tools.add(lineTool);
})(); //End of code isolation

View file

@ -24,7 +24,8 @@
* @licend
*/
(function () { //Code isolation
(function () {
//Code isolation
// Allocate the full maximum server update rate to pencil messages.
// This feels a bit risky in terms of dropped messages, but any less
@ -32,7 +33,9 @@
// seems to work, either because writing tends to happen in bursts, or
// maybe because the messages are sent when the time interval is *greater*
// than this?
var MIN_PENCIL_INTERVAL_MS = Tools.server_config.MAX_EMIT_COUNT_PERIOD / Tools.server_config.MAX_EMIT_COUNT;
var MIN_PENCIL_INTERVAL_MS =
Tools.server_config.MAX_EMIT_COUNT_PERIOD /
Tools.server_config.MAX_EMIT_COUNT;
var AUTO_FINGER_WHITEOUT = Tools.server_config.AUTO_FINGER_WHITEOUT;
var hasUsedStylus = false;
@ -43,7 +46,7 @@
//The data of the message that will be sent for every new point
function PointMessage(x, y) {
this.type = 'child';
this.type = "child";
this.parent = curLineId;
this.x = x;
this.y = y;
@ -67,7 +70,6 @@
}
function startLine(x, y, evt) {
//Prevent the press from being interpreted by the browser
evt.preventDefault();
@ -76,11 +78,11 @@
curLineId = Tools.generateUID("l"); //"l" for line
Tools.drawAndSend({
'type': 'line',
'id': curLineId,
'color': (pencilTool.secondary.active ? "#ffffff" : Tools.getColor()),
'size': Tools.getSize(),
'opacity': (pencilTool.secondary.active ? 1 : Tools.getOpacity()),
type: "line",
id: curLineId,
color: pencilTool.secondary.active ? "#ffffff" : Tools.getColor(),
size: Tools.getSize(),
opacity: pencilTool.secondary.active ? 1 : Tools.getOpacity(),
});
//Immediatly add a point to the line
@ -90,7 +92,10 @@
function continueLine(x, y, evt) {
/*Wait 70ms before adding any point to the currently drawing line.
This allows the animation to be smother*/
if (curLineId !== "" && performance.now() - lastTime > MIN_PENCIL_INTERVAL_MS) {
if (
curLineId !== "" &&
performance.now() - lastTime > MIN_PENCIL_INTERVAL_MS
) {
Tools.drawAndSend(new PointMessage(x, y));
lastTime = performance.now();
}
@ -115,10 +120,16 @@
renderingLine = createLine(data);
break;
case "child":
var line = (renderingLine.id === data.parent) ? renderingLine : svg.getElementById(data.parent);
var line =
renderingLine.id === data.parent
? renderingLine
: svg.getElementById(data.parent);
if (!line) {
console.error("Pencil: Hmmm... I received a point of a line that has not been created (%s).", data.parent);
line = renderingLine = createLine({ "id": data.parent }); //create a new line in order not to loose the points
console.error(
"Pencil: Hmmm... I received a point of a line that has not been created (%s).",
data.parent,
);
line = renderingLine = createLine({ id: data.parent }); //create a new line in order not to loose the points
}
addPoint(line, data.x, data.y);
break;
@ -151,12 +162,16 @@
function createLine(lineData) {
//Creates a new line on the canvas, or update a line that already exists with new information
var line = svg.getElementById(lineData.id) || Tools.createSVGElement("path");
var line =
svg.getElementById(lineData.id) || Tools.createSVGElement("path");
line.id = lineData.id;
//If some data is not provided, choose default value. The line may be updated later
line.setAttribute("stroke", lineData.color || "black");
line.setAttribute("stroke-width", lineData.size || 10);
line.setAttribute("opacity", Math.max(0.1, Math.min(1, lineData.opacity)) || 1);
line.setAttribute(
"opacity",
Math.max(0.1, Math.min(1, lineData.opacity)) || 1,
);
Tools.drawingArea.appendChild(line);
return line;
}
@ -189,43 +204,42 @@
}
var pencilTool = {
"name": "Pencil",
"shortcut": "p",
"listeners": {
"press": startLine,
"move": continueLine,
"release": stopLineAt,
name: "Pencil",
shortcut: "p",
listeners: {
press: startLine,
move: continueLine,
release: stopLineAt,
},
"draw": draw,
"onstart": function(oldTool) {
draw: draw,
onstart: function (oldTool) {
//Reset stylus
hasUsedStylus = false;
},
"secondary": {
"name": "White-out",
"icon": "tools/pencil/whiteout_tape.svg",
"active": false,
"switch": function() {
secondary: {
name: "White-out",
icon: "tools/pencil/whiteout_tape.svg",
active: false,
switch: function () {
stopLine();
toggleSize();
},
},
"onstart": function() {
onstart: function () {
//When switching from another tool to white-out, restore white-out size
if (pencilTool.secondary.active) {
restoreWhiteOutSize();
}
},
"onquit": function() {
onquit: function () {
//When switching from white-out to another tool, restore drawing size
if (pencilTool.secondary.active) {
restoreDrawingSize();
}
},
"mouseCursor": "url('tools/pencil/cursor.svg'), crosshair",
"icon": "tools/pencil/icon.svg",
"stylesheet": "tools/pencil/pencil.css",
mouseCursor: "url('tools/pencil/cursor.svg'), crosshair",
icon: "tools/pencil/icon.svg",
stylesheet: "tools/pencil/pencil.css",
};
Tools.add(pencilTool);
})(); //End of code isolation

View file

@ -40,9 +40,12 @@
case 2: //There are two points. The initial move and a line of zero length to make it visible
//Draw a curve that is segment between the old point and the new one
npoint = new PathDataPoint("C", [
pts[0].values[0], pts[0].values[1],
x, y,
x, y,
pts[0].values[0],
pts[0].values[1],
x,
y,
x,
y,
]);
break;
default: //There are at least two points in the line
@ -62,10 +65,9 @@
var ante_x = ante_values[ante_values.length - 2];
var ante_y = ante_values[ante_values.length - 1];
//We don't want to add the same point twice consecutively
if ((prev_x === x && prev_y === y)
|| (ante_x === x && ante_y === y)) return;
if ((prev_x === x && prev_y === y) || (ante_x === x && ante_y === y))
return;
var vectx = x - ante_x,
vecty = y - ante_y;
@ -82,12 +84,8 @@
prev_values[2] = cx1;
prev_values[3] = cy1;
return new PathDataPoint("C", [
cx2, cy2,
x, y,
x, y,
]);
return new PathDataPoint("C", [cx2, cy2, x, y, x, y]);
}
global["wboPencilPoint"] = wboPencilPoint;
})("object" === typeof module && module.exports || window);
})(("object" === typeof module && module.exports) || window);

View file

@ -24,37 +24,38 @@
* @licend
*/
(function () { //Code isolation
(function () {
//Code isolation
//Indicates the id of the shape the user is currently drawing or an empty string while the user is not drawing
var end = false,
curId = "",
curUpdate = { //The data of the message that will be sent for every new point
'type': 'update',
'id': "",
'x': 0,
'y': 0,
'x2': 0,
'y2': 0
curUpdate = {
//The data of the message that will be sent for every new point
type: "update",
id: "",
x: 0,
y: 0,
x2: 0,
y2: 0,
},
lastTime = performance.now(); //The time at which the last point was drawn
function start(x, y, evt) {
//Prevent the press from being interpreted by the browser
evt.preventDefault();
curId = Tools.generateUID("r"); //"r" for rectangle
Tools.drawAndSend({
'type': 'rect',
'id': curId,
'color': Tools.getColor(),
'size': Tools.getSize(),
'opacity': Tools.getOpacity(),
'x': x,
'y': y,
'x2': x,
'y2': y
type: "rect",
id: curId,
color: Tools.getColor(),
size: Tools.getSize(),
opacity: Tools.getOpacity(),
x: x,
y: y,
x2: x,
y2: y,
});
curUpdate.id = curId;
@ -73,7 +74,8 @@
x = curUpdate.x + (dx > 0 ? d : -d);
y = curUpdate.y + (dy > 0 ? d : -d);
}
curUpdate['x2'] = x; curUpdate['y2'] = y;
curUpdate["x2"] = x;
curUpdate["y2"] = y;
if (performance.now() - lastTime > 70 || end) {
Tools.drawAndSend(curUpdate);
lastTime = performance.now();
@ -99,19 +101,26 @@
createShape(data);
break;
case "update":
var shape = svg.getElementById(data['id']);
var shape = svg.getElementById(data["id"]);
if (!shape) {
console.error("Straight shape: Hmmm... I received a point of a rect that has not been created (%s).", data['id']);
createShape({ //create a new shape in order not to loose the points
"id": data['id'],
"x": data['x2'],
"y": data['y2']
console.error(
"Straight shape: Hmmm... I received a point of a rect that has not been created (%s).",
data["id"],
);
createShape({
//create a new shape in order not to loose the points
id: data["id"],
x: data["x2"],
y: data["y2"],
});
}
updateShape(shape, data);
break;
default:
console.error("Straight shape: Draw instruction with unknown type. ", data);
console.error(
"Straight shape: Draw instruction with unknown type. ",
data,
);
break;
}
}
@ -125,36 +134,38 @@
//If some data is not provided, choose default value. The shape may be updated later
shape.setAttribute("stroke", data.color || "black");
shape.setAttribute("stroke-width", data.size || 10);
shape.setAttribute("opacity", Math.max(0.1, Math.min(1, data.opacity)) || 1);
shape.setAttribute(
"opacity",
Math.max(0.1, Math.min(1, data.opacity)) || 1,
);
Tools.drawingArea.appendChild(shape);
return shape;
}
function updateShape(shape, data) {
shape.x.baseVal.value = Math.min(data['x2'], data['x']);
shape.y.baseVal.value = Math.min(data['y2'], data['y']);
shape.width.baseVal.value = Math.abs(data['x2'] - data['x']);
shape.height.baseVal.value = Math.abs(data['y2'] - data['y']);
shape.x.baseVal.value = Math.min(data["x2"], data["x"]);
shape.y.baseVal.value = Math.min(data["y2"], data["y"]);
shape.width.baseVal.value = Math.abs(data["x2"] - data["x"]);
shape.height.baseVal.value = Math.abs(data["y2"] - data["y"]);
}
var rectangleTool = {
"name": "Rectangle",
"shortcut": "r",
"listeners": {
"press": start,
"move": move,
"release": stop,
name: "Rectangle",
shortcut: "r",
listeners: {
press: start,
move: move,
release: stop,
},
"secondary": {
"name": "Square",
"icon": "tools/rect/icon-square.svg",
"active": false,
secondary: {
name: "Square",
icon: "tools/rect/icon-square.svg",
active: false,
},
"draw": draw,
"mouseCursor": "crosshair",
"icon": "tools/rect/icon.svg",
"stylesheet": "tools/rect/rect.css"
draw: draw,
mouseCursor: "crosshair",
icon: "tools/rect/icon.svg",
stylesheet: "tools/rect/rect.css",
};
Tools.add(rectangleTool);
})(); //End of code isolation

View file

@ -1,16 +1,15 @@
#textToolInput {
position:fixed;
top:-1000px; /*Hidden*/
position: fixed;
top: -1000px; /*Hidden*/
left: 80px;
width:500px;
width: 500px;
}
#textToolInput:focus {
top: 5px;
}
text {
font-family:"Arial", "Helvetica", sans-serif;
user-select:none;
-moz-user-select:none;
font-family: "Arial", "Helvetica", sans-serif;
user-select: none;
-moz-user-select: none;
}

View file

@ -24,7 +24,8 @@
* @licend
*/
(function () { //Code isolation
(function () {
//Code isolation
var board = Tools.board;
var input = document.createElement("input");
@ -33,21 +34,20 @@
input.setAttribute("autocomplete", "off");
var curText = {
"x": 0,
"y": 0,
"size": 36,
"rawSize": 16,
"oldSize": 0,
"opacity": 1,
"color": "#000",
"id": 0,
"sentText": "",
"lastSending": 0
x: 0,
y: 0,
size: 36,
rawSize: 16,
oldSize: 0,
opacity: 1,
color: "#000",
id: 0,
sentText: "",
lastSending: 0,
};
var active = false;
function onStart() {
curText.oldSize = Tools.getSize();
Tools.setSize(curText.rawSize);
@ -82,7 +82,8 @@
curText.id = elem.id;
var r = elem.getBoundingClientRect();
var x = (r.left + document.documentElement.scrollLeft) / Tools.scale;
var y = (r.top + r.height + document.documentElement.scrollTop) / Tools.scale;
var y =
(r.top + r.height + document.documentElement.scrollTop) / Tools.scale;
curText.x = x;
curText.y = y;
@ -98,15 +99,19 @@
active = true;
if (!input.parentNode) board.appendChild(input);
input.value = "";
var left = curText.x - document.documentElement.scrollLeft + 'px';
var clientW = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
var left = curText.x - document.documentElement.scrollLeft + "px";
var clientW = Math.max(
document.documentElement.clientWidth,
window.innerWidth || 0,
);
var x = curText.x * Tools.scale - document.documentElement.scrollLeft;
if (x + 250 > clientW) {
x = Math.max(60, clientW - 260)
x = Math.max(60, clientW - 260);
}
input.style.left = x + 'px';
input.style.top = curText.y * Tools.scale - document.documentElement.scrollTop + 20 + 'px';
input.style.left = x + "px";
input.style.top =
curText.y * Tools.scale - document.documentElement.scrollTop + 20 + "px";
input.focus();
input.addEventListener("keyup", textChangeHandler);
input.addEventListener("blur", textChangeHandler);
@ -114,7 +119,11 @@
}
function stopEdit() {
try { input.blur(); } catch (e) { /* Internet Explorer */ }
try {
input.blur();
} catch (e) {
/* Internet Explorer */
}
active = false;
blur();
curText.id = 0;
@ -125,15 +134,17 @@
function blur() {
if (active) return;
input.style.top = '-1000px';
input.style.top = "-1000px";
}
function textChangeHandler(evt) {
if (evt.which === 13) { // enter
if (evt.which === 13) {
// enter
curText.y += 1.5 * curText.size;
stopEdit();
startEdit();
} else if (evt.which === 27) { // escape
} else if (evt.which === 27) {
// escape
stopEdit();
}
if (performance.now() - curText.lastSending > 100) {
@ -142,19 +153,19 @@
if (curText.id === 0) {
curText.id = Tools.generateUID("t"); //"t" for text
Tools.drawAndSend({
'type': 'new',
'id': curText.id,
'color': curText.color,
'size': curText.size,
'opacity': curText.opacity,
'x': curText.x,
'y': curText.y
})
type: "new",
id: curText.id,
color: curText.color,
size: curText.size,
opacity: curText.opacity,
x: curText.x,
y: curText.y,
});
}
Tools.drawAndSend({
'type': "update",
'id': curText.id,
'txt': input.value.slice(0, 280)
type: "update",
id: curText.id,
txt: input.value.slice(0, 280),
});
curText.sentText = input.value;
curText.lastSending = performance.now();
@ -174,7 +185,9 @@
case "update":
var textField = document.getElementById(data.id);
if (textField === null) {
console.error("Text: Hmmm... I received text that belongs to an unknown text field");
console.error(
"Text: Hmmm... I received text that belongs to an unknown text field",
);
return false;
}
updateText(textField, data.txt);
@ -196,24 +209,27 @@
elem.setAttribute("y", fieldData.y);
elem.setAttribute("font-size", fieldData.size);
elem.setAttribute("fill", fieldData.color);
elem.setAttribute("opacity", Math.max(0.1, Math.min(1, fieldData.opacity)) || 1);
elem.setAttribute(
"opacity",
Math.max(0.1, Math.min(1, fieldData.opacity)) || 1,
);
if (fieldData.txt) elem.textContent = fieldData.txt;
Tools.drawingArea.appendChild(elem);
return elem;
}
Tools.add({ //The new tool
"name": "Text",
"shortcut": "t",
"listeners": {
"press": clickHandler,
Tools.add({
//The new tool
name: "Text",
shortcut: "t",
listeners: {
press: clickHandler,
},
"onstart": onStart,
"onquit": onQuit,
"draw": draw,
"stylesheet": "tools/text/text.css",
"icon": "tools/text/icon.svg",
"mouseCursor": "text"
onstart: onStart,
onquit: onQuit,
draw: draw,
stylesheet: "tools/text/text.css",
icon: "tools/text/icon.svg",
mouseCursor: "text",
});
})(); //End of code isolation

View file

@ -24,24 +24,26 @@
* @licend
*/
(function () { //Code isolation
var ZOOM_FACTOR = .5;
(function () {
//Code isolation
var ZOOM_FACTOR = 0.5;
var origin = {
scrollX: document.documentElement.scrollLeft,
scrollY: document.documentElement.scrollTop,
x: 0.0,
y: 0.0,
clientY: 0,
scale: 1.0
scale: 1.0,
};
var moved = false, pressed = false;
var moved = false,
pressed = false;
function zoom(origin, scale) {
var oldScale = origin.scale;
var newScale = Tools.setScale(scale);
window.scrollTo(
origin.scrollX + origin.x * (newScale - oldScale),
origin.scrollY + origin.y * (newScale - oldScale)
origin.scrollY + origin.y * (newScale - oldScale),
);
}
@ -73,7 +75,7 @@
if (pressed) {
evt.preventDefault();
var delta = getClientY(evt, isTouchEvent) - origin.clientY;
var scale = origin.scale * (1 + delta * ZOOM_FACTOR / 100);
var scale = origin.scale * (1 + (delta * ZOOM_FACTOR) / 100);
if (Math.abs(delta) > 1) moved = true;
animation = animate(scale);
}
@ -82,10 +84,13 @@
function onwheel(evt) {
evt.preventDefault();
var multiplier =
(evt.deltaMode === WheelEvent.DOM_DELTA_LINE) ? 30 :
(evt.deltaMode === WheelEvent.DOM_DELTA_PAGE) ? 1000 :
1;
var deltaX = evt.deltaX * multiplier, deltaY = evt.deltaY * multiplier;
evt.deltaMode === WheelEvent.DOM_DELTA_LINE
? 30
: evt.deltaMode === WheelEvent.DOM_DELTA_PAGE
? 1000
: 1;
var deltaX = evt.deltaX * multiplier,
deltaY = evt.deltaY * multiplier;
if (!evt.ctrlKey) {
// zoom
var scale = Tools.getScale();
@ -97,23 +102,33 @@
// make finer changes if shift is being held
var change = evt.shiftKey ? 1 : 5;
// change tool size
Tools.setSize(Tools.getSize() - deltaY / 100 * change);
Tools.setSize(Tools.getSize() - (deltaY / 100) * change);
} else if (evt.shiftKey) {
// scroll horizontally
window.scrollTo(document.documentElement.scrollLeft + deltaY, document.documentElement.scrollTop + deltaX);
window.scrollTo(
document.documentElement.scrollLeft + deltaY,
document.documentElement.scrollTop + deltaX,
);
} else {
// regular scrolling
window.scrollTo(document.documentElement.scrollLeft + deltaX, document.documentElement.scrollTop + deltaY);
window.scrollTo(
document.documentElement.scrollLeft + deltaX,
document.documentElement.scrollTop + deltaY,
);
}
}
Tools.board.addEventListener("wheel", onwheel, { passive: false });
Tools.board.addEventListener("touchmove", function ontouchmove(evt) {
Tools.board.addEventListener(
"touchmove",
function ontouchmove(evt) {
// 2-finger pan to zoom
var touches = evt.touches;
if (touches.length === 2) {
var x0 = touches[0].clientX, x1 = touches[1].clientX,
y0 = touches[0].clientY, y1 = touches[1].clientY,
var x0 = touches[0].clientX,
x1 = touches[1].clientX,
y0 = touches[0].clientY,
y1 = touches[1].clientY,
dx = x0 - x1,
dy = y0 - y1;
var x = (touches[0].pageX + touches[1].pageX) / 2 / Tools.getScale(),
@ -125,11 +140,13 @@
origin.distance = distance;
} else {
var delta = distance - origin.distance;
var scale = origin.scale * (1 + delta * ZOOM_FACTOR / 100);
var scale = origin.scale * (1 + (delta * ZOOM_FACTOR) / 100);
animate(scale);
}
}
}, { passive: true });
},
{ passive: true },
);
function touchend() {
pressed = false;
}
@ -138,7 +155,7 @@
function release(x, y, evt, isTouchEvent) {
if (pressed && !moved) {
var delta = (evt.shiftKey === true) ? -1 : 1;
var delta = evt.shiftKey === true ? -1 : 1;
var scale = Tools.getScale() * (1 + delta * ZOOM_FACTOR);
zoom(origin, scale);
}
@ -150,7 +167,7 @@
if (evt.key === "Shift") {
Tools.svg.style.cursor = "zoom-" + (down ? "out" : "in");
}
}
};
}
function getClientY(evt, isTouchEvent) {
@ -170,19 +187,19 @@
}
var zoomTool = {
"name": "Zoom",
"shortcut": "z",
"listeners": {
"press": press,
"move": move,
"release": release,
name: "Zoom",
shortcut: "z",
listeners: {
press: press,
move: move,
release: release,
},
"onstart": onstart,
"onquit": onquit,
"mouseCursor": "zoom-in",
"icon": "tools/zoom/icon.svg",
"helpText": "click_to_zoom",
"showMarker": true,
onstart: onstart,
onquit: onquit,
mouseCursor: "zoom-in",
icon: "tools/zoom/icon.svg",
helpText: "click_to_zoom",
showMarker: true,
};
Tools.add(zoomTool);
})(); //End of code isolation

View file

@ -1,10 +1,10 @@
version: '3'
version: "3"
services:
www:
volumes:
- /opt/app/server-data:/opt/app/server-data
ports:
- '80:80'
- "80:80"
build:
context: .
restart: on-failure
@ -14,4 +14,3 @@ services:
delay: 5s
max_attempts: 5
window: 60s

View file

@ -1,49 +1,49 @@
// Autogenerated by Nightwatch
// Refer to the online docs for more details: https://nightwatchjs.org/gettingstarted/configuration/
const Services = {}; loadServices();
const Services = {};
loadServices();
module.exports = {
"src_folders": ["tests"],
src_folders: ["tests"],
"webdriver": {
"start_process": true,
"server_path": "./node_modules/.bin/geckodriver",
"cli_args": [
"--log", "debug"
],
"port": 4444
webdriver: {
start_process: true,
server_path: "./node_modules/.bin/geckodriver",
cli_args: ["--log", "debug"],
port: 4444,
},
"test_settings": {
"default": {
"desiredCapabilities": {
"browserName": "firefox",
"acceptInsecureCerts": true,
"alwaysMatch": {
test_settings: {
default: {
desiredCapabilities: {
browserName: "firefox",
acceptInsecureCerts: true,
alwaysMatch: {
"moz:firefoxOptions": {
"args": ["-headless"]
}
}
}
args: ["-headless"],
},
},
},
},
jwt: {
globals: {
token:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.5mhBHqs5_DTLdINd9p5m7ZJ6XD0Xc55kIaCRY5r6HRA",
},
},
},
"jwt": {
"globals": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.5mhBHqs5_DTLdINd9p5m7ZJ6XD0Xc55kIaCRY5r6HRA"
}
}
}
};
function loadServices() {
try {
Services.seleniumServer = require('selenium-server');
} catch (err) { }
Services.seleniumServer = require("selenium-server");
} catch (err) {}
try {
Services.chromedriver = require('chromedriver');
} catch (err) { }
Services.chromedriver = require("chromedriver");
} catch (err) {}
try {
Services.geckodriver = require('geckodriver');
} catch (err) { }
Services.geckodriver = require("geckodriver");
} catch (err) {}
}

24
package-lock.json generated
View file

@ -21,7 +21,8 @@
},
"devDependencies": {
"geckodriver": "^4.2.1",
"nightwatch": "^3.2.1"
"nightwatch": "^3.2.1",
"prettier": "^3.1.0"
}
},
"node_modules/@assemblyscript/loader": {
@ -3959,6 +3960,21 @@
"node": ">=8"
}
},
"node_modules/prettier": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@ -8397,6 +8413,12 @@
"toposort": "^2.0.2"
}
},
"prettier": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
"dev": true
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",

View file

@ -20,7 +20,9 @@
},
"scripts": {
"start": "node ./server/server.js",
"test": "nightwatch tests && nightwatch tests --env jwt"
"test": "nightwatch tests && nightwatch tests --env jwt",
"prettier": "prettier . --write",
"prettier-check": "prettier . --check"
},
"main": "./server/server.js",
"repository": {
@ -29,6 +31,7 @@
},
"devDependencies": {
"geckodriver": "^4.2.1",
"nightwatch": "^3.2.1"
"nightwatch": "^3.2.1",
"prettier": "^3.1.0"
}
}

View file

@ -46,7 +46,7 @@ class BoardData {
this.board = {};
this.file = path.join(
config.HISTORY_DIR,
"board-" + encodeURIComponent(name) + ".json"
"board-" + encodeURIComponent(name) + ".json",
);
this.assetsDir = path.join(
config.HISTORY_DIR,
@ -202,7 +202,7 @@ class BoardData {
this.addChild(message.parent, message);
break;
case "clear":
if(jwtauth.roleInBoard(message.token,message.board) === 'moderator') {
if (jwtauth.roleInBoard(message.token, message.board) === "moderator") {
this.clear();
} else {
throw new Error("User is not a moderator");

View file

@ -24,7 +24,8 @@ async function get_error(directory) {
let err_msg = "does not allow file creation and deletion. ";
try {
const { uid, gid } = os.userInfo();
err_msg += "Check the permissions of the directory, and if needed change them so that " +
err_msg +=
"Check the permissions of the directory, and if needed change them so that " +
`user with UID ${uid} has access to them. This can be achieved by running the command: chown ${uid}:${gid} on the directory`;
} finally {
return err_msg;
@ -40,7 +41,7 @@ async function get_error(directory) {
fileChecks.push(
fs.promises.access(elemPath, R_OK | W_OK).catch(function () {
return elemPath;
})
}),
);
}
}
@ -67,7 +68,7 @@ function check_output_directory(directory) {
console.error(
`The configured history directory in which boards are stored ${error}.` +
`\nThe history directory can be configured with the environment variable WBO_HISTORY_DIR. ` +
`It is currently set to "${directory}".`
`It is currently set to "${directory}".`,
);
process.exit(1);
}

View file

@ -46,7 +46,9 @@ module.exports = {
BLOCKED_TOOLS: (process.env["WBO_BLOCKED_TOOLS"] || "").split(","),
/** Selection Buttons. A comma-separated list of selection buttons that should not be available. */
BLOCKED_SELECTION_BUTTONS: (process.env["WBO_BLOCKED_SELECTION_BUTTONS"] || "").split(","),
BLOCKED_SELECTION_BUTTONS: (
process.env["WBO_BLOCKED_SELECTION_BUTTONS"] || ""
).split(","),
/** Automatically switch to White-out on finger touch after drawing
with Pencil using a stylus. Only supported on iPad with Apple Pencil. */
@ -59,9 +61,8 @@ module.exports = {
STATSD_URL: process.env["STATSD_URL"],
/** Secret key for jwt */
AUTH_SECRET_KEY: (process.env["AUTH_SECRET_KEY"] || ""),
AUTH_SECRET_KEY: process.env["AUTH_SECRET_KEY"] || "",
/** If this variable is set, automatically redirect to this board from the root of the application. */
DEFAULT_BOARD: (process.env["WBO_DEFAULT_BOARD"]),
DEFAULT_BOARD: process.env["WBO_DEFAULT_BOARD"],
};

View file

@ -1,7 +1,7 @@
const fs = require("./fs_promises.js"),
path = require("path"),
wboPencilPoint = require("../client-data/tools/pencil/wbo_pencil_point.js")
.wboPencilPoint;
wboPencilPoint =
require("../client-data/tools/pencil/wbo_pencil_point.js").wboPencilPoint;
function htmlspecialchars(str) {
if (typeof str !== "string") return "";
@ -181,7 +181,7 @@ async function toSVG(obj, writeable) {
Math.max((elem.y + margin + (elem.deltay | 0)) | 0, dim[1]),
];
},
[margin, margin]
[margin, margin],
);
writeable.write(
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" ' +
@ -194,7 +194,7 @@ async function toSVG(obj, writeable) {
'text {font-family:"Arial"}' +
"path {fill:none;stroke-linecap:round;stroke-linejoin:round;}" +
"rect {fill:none}" +
"]]></style></defs>"
"]]></style></defs>",
);
await Promise.all(
elems.map(async function (elem) {
@ -202,7 +202,7 @@ async function toSVG(obj, writeable) {
const renderFun = Tools[elem.tool];
if (renderFun) writeable.write(renderFun(elem));
else console.warn("Missing render function for tool", elem.tool);
})
}),
);
writeable.write("</svg>");
}

View file

@ -24,8 +24,8 @@
* @licend
*/
config = require("./configuration.js"),
jsonwebtoken = require("jsonwebtoken");
(config = require("./configuration.js")),
(jsonwebtoken = require("jsonwebtoken"));
/**
* This function checks if a board name is set in the roles claim.
@ -38,14 +38,14 @@ config = require("./configuration.js"),
function checkBoardnameInToken(url, boardNameIn) {
var token = url.searchParams.get("token");
if (roleInBoard(token, boardNameIn) === 'forbidden') {
if (roleInBoard(token, boardNameIn) === "forbidden") {
throw new Error("Acess Forbidden");
}
}
function parse_role(role) {
let [_, role_name, board_name] = role.match(/^([^:]*):?(.*)$/);
return {role_name, board_name}
return { role_name, board_name };
}
/**
@ -70,7 +70,7 @@ function roleInBoard(token, board = null) {
for (var line of roles) {
var role = parse_role(line);
if (role.board_name !== '') {
if (role.board_name !== "") {
oneHasBoardName = true;
}
if (role.role_name === "moderator") {
@ -96,4 +96,4 @@ function roleInBoard(token, board = null) {
}
}
module.exports = {checkBoardnameInToken, roleInBoard};
module.exports = { checkBoardnameInToken, roleInBoard };

View file

@ -24,10 +24,9 @@
* @licend
*/
config = require("./configuration.js"),
jsonwebtoken = require("jsonwebtoken");
const {roleInBoard} = require("./jwtBoardnameAuth");
(config = require("./configuration.js")),
(jsonwebtoken = require("jsonwebtoken"));
const { roleInBoard } = require("./jwtBoardnameAuth");
/**
* Validates jwt and returns whether user is a moderator
* @param {URL} url
@ -48,5 +47,4 @@ function checkUserPermission(url) {
return isModerator;
}
module.exports = { checkUserPermission };

View file

@ -1,6 +1,6 @@
var app = require("http").createServer(handler),
sockets = require("./sockets.js"),
{log, monitorFunction} = require("./log.js"),
{ log, monitorFunction } = require("./log.js"),
path = require("path"),
fs = require("./fs_promises.js"),
crypto = require("crypto"),
@ -25,7 +25,7 @@ if (parseFloat(process.versions.node) < MIN_NODE_VERSION) {
process.version +
", wbo requires at least " +
MIN_NODE_VERSION +
" !!!"
" !!!",
);
}
@ -109,10 +109,10 @@ function handler(request, response) {
}
const boardTemplate = new templating.BoardTemplate(
path.join(config.WEBROOT, "board.html")
path.join(config.WEBROOT, "board.html"),
);
const indexTemplate = new templating.Template(
path.join(config.WEBROOT, "index.html")
path.join(config.WEBROOT, "index.html"),
);
/**
@ -145,10 +145,10 @@ async function handleRequest(request, response) {
if (parts[0] === "") parts.shift();
var fileExt = path.extname(parsedUrl.pathname);
var staticResources = ['.js','.css', '.svg', '.ico', '.png', '.jpg', 'gif'];
var staticResources = [".js", ".css", ".svg", ".ico", ".png", ".jpg", "gif"];
// If we're not being asked for a file, then we should check permissions.
var isModerator = false;
if(!staticResources.includes(fileExt)) {
if (!staticResources.includes(fileExt)) {
isModerator = jwtauth.checkUserPermission(parsedUrl);
}
@ -177,7 +177,7 @@ async function handleRequest(request, response) {
var boardName = validateBoardName(parts[1]),
history_file = path.join(
config.HISTORY_DIR,
"board-" + boardName + ".json"
"board-" + boardName + ".json",
);
jwtBoardName.checkBoardnameInToken(parsedUrl, boardName);
if (parts.length > 2 && /^[0-9A-Za-z.\-]+$/.test(parts[2])) {
@ -260,7 +260,7 @@ async function handleRequest(request, response) {
var boardName = validateBoardName(parts[1]),
history_file = path.join(
config.HISTORY_DIR,
"board-" + boardName + ".json"
"board-" + boardName + ".json",
);
jwtBoardName.checkBoardnameInToken(parsedUrl, boardName);
response.writeHead(200, {
@ -310,7 +310,7 @@ async function handleRequest(request, response) {
.then(function (bundleString) {
response.setHeader(
"Cache-Control",
"private, max-age=172800, stale-while-revalidate=1728000"
"private, max-age=172800, stale-while-revalidate=1728000",
);
response.setHeader("Vary", "User-Agent");
response.setHeader("Content-Type", "application/javascript");
@ -321,10 +321,11 @@ async function handleRequest(request, response) {
case "": // Index page
logRequest(request);
if (config.DEFAULT_BOARD) {
response.writeHead(302, { Location: 'boards/' + encodeURIComponent(config.DEFAULT_BOARD) });
response.writeHead(302, {
Location: "boards/" + encodeURIComponent(config.DEFAULT_BOARD),
});
response.end(name);
} else
indexTemplate.serve(request, response);
} else indexTemplate.serve(request, response);
break;
default:

View file

@ -33,12 +33,17 @@ function startIO(app, boardDataList) {
io = iolib(app);
if (config.AUTH_SECRET_KEY) {
// Middleware to check for valid jwt
io.use(function(socket, next) {
if(socket.handshake.query && socket.handshake.query.token) {
jsonwebtoken.verify(socket.handshake.query.token, config.AUTH_SECRET_KEY, function(err, decoded) {
if(err) return next(new Error("Authentication error: Invalid JWT"));
io.use(function (socket, next) {
if (socket.handshake.query && socket.handshake.query.token) {
jsonwebtoken.verify(
socket.handshake.query.token,
config.AUTH_SECRET_KEY,
function (err, decoded) {
if (err)
return next(new Error("Authentication error: Invalid JWT"));
next();
})
},
);
} else {
next(new Error("Authentication error: No jwt provided"));
}
@ -92,7 +97,7 @@ function handleSocketConnection(socket) {
"error",
noFail(function onSocketError(error) {
log("ERROR", error);
})
}),
);
socket.on("getboard", async function onGetBoard(name) {
@ -152,7 +157,7 @@ function handleSocketConnection(socket) {
//Send data to all other users connected on the same board
socket.broadcast.to(boardName).emit("broadcast", data);
})
}),
);
socket.on("disconnecting", function onDisconnecting(reason) {

View file

@ -11,7 +11,7 @@ const client_config = require("./client_configuration");
* @type {object}
*/
const TRANSLATIONS = JSON.parse(
fs.readFileSync(path.join(__dirname, "translations.json"))
fs.readFileSync(path.join(__dirname, "translations.json")),
);
const languages = Object.keys(TRANSLATIONS);
@ -33,7 +33,8 @@ class Template {
this.template = handlebars.compile(contents);
}
parameters(parsedUrl, request, isModerator) {
const accept_language_str = parsedUrl.query.lang || request.headers["accept-language"];
const accept_language_str =
parsedUrl.query.lang || request.headers["accept-language"];
const accept_languages = accept_language_parser.parse(accept_language_str);
const opts = { loose: true };
let language =
@ -41,7 +42,8 @@ class Template {
// The loose matcher returns the first language that partially matches, so we need to
// check if the preferred language is supported to return it
if (accept_languages.length > 0) {
const preferred_language = accept_languages[0].code + "-" + accept_languages[0].region;
const preferred_language =
accept_languages[0].code + "-" + accept_languages[0].region;
if (languages.includes(preferred_language)) {
language = preferred_language;
}
@ -51,7 +53,14 @@ class Template {
const prefix = request.url.split("/boards/")[0].substr(1);
const baseUrl = findBaseUrl(request) + (prefix ? prefix + "/" : "");
const moderator = isModerator;
return { baseUrl, languages, language, translations, configuration, moderator };
return {
baseUrl,
languages,
language,
translations,
configuration,
moderator,
};
}
serve(request, response, isModerator) {
const parsedUrl = url.parse(request.url, true);

View file

@ -2,17 +2,18 @@ const fs = require("../server/fs_promises.js");
const os = require("os");
const path = require("path");
const PORT = 8487
const SERVER = 'http://localhost:' + PORT;
const PORT = 8487;
const SERVER = "http://localhost:" + PORT;
let wbo, data_path, tokenQuery, currentPath;
async function beforeEach(browser, done) {
data_path = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'wbo-test-data-'));
currentPath = process.cwd();
data_path = await fs.promises.mkdtemp(
path.join(os.tmpdir(), "wbo-test-data-"),
);
process.env["PORT"] = PORT;
process.env["WBO_HISTORY_DIR"] = data_path;
if(browser.globals.token) {
if (browser.globals.token) {
process.env["AUTH_SECRET_KEY"] = "test";
tokenQuery = "token=" + browser.globals.token;
}
@ -27,23 +28,25 @@ async function afterEach(browser, done) {
}
function testPencil(browser) {
return browser
.assert.titleContains('WBO')
.click('.tool[title ~= Crayon]') // pencil
.assert.cssClassPresent('.tool[title ~= Crayon]', ['curTool'])
return browser.assert
.titleContains("WBO")
.click(".tool[title ~= Crayon]") // pencil
.assert.cssClassPresent(".tool[title ~= Crayon]", ["curTool"])
.executeAsync(async function (done) {
function sleep(t) {
return new Promise(function (accept) { setTimeout(accept, t); });
return new Promise(function (accept) {
setTimeout(accept, t);
});
}
// A straight path with just two points
Tools.setColor('#123456');
Tools.setColor("#123456");
Tools.curTool.listeners.press(100, 200, new Event("mousedown"));
await sleep(80);
Tools.curTool.listeners.release(300, 400, new Event("mouseup"));
// A line with three points that form an "U" shape
await sleep(80);
Tools.setColor('#abcdef');
Tools.setColor("#abcdef");
Tools.curTool.listeners.press(0, 0, new Event("mousedown"));
await sleep(80);
Tools.curTool.listeners.move(90, 120, new Event("mousemove"));
@ -51,22 +54,34 @@ function testPencil(browser) {
Tools.curTool.listeners.release(180, 0, new Event("mouseup"));
done();
})
.assert.visible("path[d='M 100 200 L 100 200 C 100 200 300 400 300 400'][stroke='#123456']")
.assert.visible("path[d='M 0 0 L 0 0 C 0 0 40 120 90 120 C 140 120 180 0 180 0'][stroke='#abcdef']")
.assert.visible(
"path[d='M 100 200 L 100 200 C 100 200 300 400 300 400'][stroke='#123456']",
)
.assert.visible(
"path[d='M 0 0 L 0 0 C 0 0 40 120 90 120 C 140 120 180 0 180 0'][stroke='#abcdef']",
)
.refresh()
.waitForElementVisible("path[d='M 100 200 L 100 200 C 100 200 300 400 300 400'][stroke='#123456']")
.assert.visible("path[d='M 0 0 L 0 0 C 0 0 40 120 90 120 C 140 120 180 0 180 0'][stroke='#abcdef']")
.url(SERVER + '/preview/anonymous?' + tokenQuery)
.waitForElementVisible("path[d='M 100 200 L 100 200 C 100 200 300 400 300 400'][stroke='#123456']")
.assert.visible("path[d='M 0 0 L 0 0 C 0 0 40 120 90 120 C 140 120 180 0 180 0'][stroke='#abcdef']")
.back()
.waitForElementVisible(
"path[d='M 100 200 L 100 200 C 100 200 300 400 300 400'][stroke='#123456']",
)
.assert.visible(
"path[d='M 0 0 L 0 0 C 0 0 40 120 90 120 C 140 120 180 0 180 0'][stroke='#abcdef']",
)
.url(SERVER + "/preview/anonymous?" + tokenQuery)
.waitForElementVisible(
"path[d='M 100 200 L 100 200 C 100 200 300 400 300 400'][stroke='#123456']",
)
.assert.visible(
"path[d='M 0 0 L 0 0 C 0 0 40 120 90 120 C 140 120 180 0 180 0'][stroke='#abcdef']",
)
.back();
}
function testCircle(browser) {
return browser
.click('#toolID-Ellipse')
.click("#toolID-Ellipse")
.executeAsync(function (done) {
Tools.setColor('#112233');
Tools.setColor("#112233");
Tools.curTool.listeners.press(200, 400, new Event("mousedown"));
setTimeout(() => {
const evt = new Event("mousemove");
@ -75,25 +90,34 @@ function testCircle(browser) {
done();
}, 100);
})
.assert.visible("ellipse[cx='0'][cy='200'][rx='200'][ry='200'][stroke='#112233']")
.assert.visible(
"ellipse[cx='0'][cy='200'][rx='200'][ry='200'][stroke='#112233']",
)
.refresh()
.waitForElementVisible("ellipse[cx='0'][cy='200'][rx='200'][ry='200'][stroke='#112233']", 15000)
.click('#toolID-Ellipse') // Click the ellipse tool
.click('#toolID-Ellipse') // Click again to toggle
.assert.containsText('#toolID-Ellipse .tool-name', 'Cercle') // Circle in french
.waitForElementVisible(
"ellipse[cx='0'][cy='200'][rx='200'][ry='200'][stroke='#112233']",
15000,
)
.click("#toolID-Ellipse") // Click the ellipse tool
.click("#toolID-Ellipse") // Click again to toggle
.assert.containsText("#toolID-Ellipse .tool-name", "Cercle"); // Circle in french
}
function testCursor(browser) {
return browser
.execute(function (done) {
Tools.setColor('#456123'); // Move the cursor over the board
Tools.setColor("#456123"); // Move the cursor over the board
var e = new Event("mousemove");
e.pageX = 150;
e.pageY = 200;
Tools.board.dispatchEvent(e)
Tools.board.dispatchEvent(e);
})
.assert.cssProperty("#cursor-me", "transform", "matrix(1, 0, 0, 1, 150, 200)")
.assert.attributeEquals("#cursor-me", "fill", "#456123")
.assert.cssProperty(
"#cursor-me",
"transform",
"matrix(1, 0, 0, 1, 150, 200)",
)
.assert.attributeEquals("#cursor-me", "fill", "#456123");
}
function testImageUpload(browser) {
@ -132,36 +156,96 @@ function testImageUpload(browser) {
}
function testBoard(browser) {
var page = browser.url(SERVER + '/boards/anonymous?lang=fr&' + tokenQuery)
.waitForElementVisible('.tool[title ~= Crayon]') // pencil
var page = browser
.url(SERVER + "/boards/anonymous?lang=fr&" + tokenQuery)
.waitForElementVisible(".tool[title ~= Crayon]"); // pencil
page = testImageUpload(page);
page = testPencil(page);
page = testCircle(page);
page = testCursor(page);
// test hideMenu
browser.url(SERVER + '/boards/anonymous?lang=fr&hideMenu=true&' + tokenQuery).waitForElementNotVisible('#menu');
browser.url(SERVER + '/boards/anonymous?lang=fr&hideMenu=false&' + tokenQuery).waitForElementVisible('#menu');
if(browser.globals.token) {
browser
.url(SERVER + "/boards/anonymous?lang=fr&hideMenu=true&" + tokenQuery)
.waitForElementNotVisible("#menu");
browser
.url(SERVER + "/boards/anonymous?lang=fr&hideMenu=false&" + tokenQuery)
.waitForElementVisible("#menu");
if (browser.globals.token) {
//has moderator jwt and no board name
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3IiXX0.PqYHmV0loeKwyLLYZ1a1eIXBCCaa3t5lYUTu_P_-i14').waitForElementVisible('#toolID-Clear');
browser
.url(
SERVER +
"/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3IiXX0.PqYHmV0loeKwyLLYZ1a1eIXBCCaa3t5lYUTu_P_-i14",
)
.waitForElementVisible("#toolID-Clear");
//has moderator JWT and other board name
browser.url(SERVER + '/boards/testboard123?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3IiXX0.PqYHmV0loeKwyLLYZ1a1eIXBCCaa3t5lYUTu_P_-i14').waitForElementVisible('#toolID-Clear');
browser
.url(
SERVER +
"/boards/testboard123?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3IiXX0.PqYHmV0loeKwyLLYZ1a1eIXBCCaa3t5lYUTu_P_-i14",
)
.waitForElementVisible("#toolID-Clear");
//has moderator JWT and board name match board name in url
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdGJvYXJkIl19.UVf6awGEChVxcWBbt6dYoNH0Scq7cVD_xfQn-U8A1lw').waitForElementVisible('#toolID-Clear');
browser
.url(
SERVER +
"/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdGJvYXJkIl19.UVf6awGEChVxcWBbt6dYoNH0Scq7cVD_xfQn-U8A1lw",
)
.waitForElementVisible("#toolID-Clear");
//has moderator JWT and board name NOT match board name in url
browser.url(SERVER + '/boards/testboard123?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdGJvYXJkIl19.UVf6awGEChVxcWBbt6dYoNH0Scq7cVD_xfQn-U8A1lw').waitForElementNotPresent('#menu');
browser
.url(
SERVER +
"/boards/testboard123?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdGJvYXJkIl19.UVf6awGEChVxcWBbt6dYoNH0Scq7cVD_xfQn-U8A1lw",
)
.waitForElementNotPresent("#menu");
//has editor JWT and no boardname provided
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0b3IiXX0.IJehwM8tPVQFzJ2fZMBHveii1DRChVtzo7PEnSmmFt8').waitForElementNotPresent('#toolID-Clear');
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0b3IiXX0.IJehwM8tPVQFzJ2fZMBHveii1DRChVtzo7PEnSmmFt8').waitForElementVisible('#menu')
browser
.url(
SERVER +
"/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0b3IiXX0.IJehwM8tPVQFzJ2fZMBHveii1DRChVtzo7PEnSmmFt8",
)
.waitForElementNotPresent("#toolID-Clear");
browser
.url(
SERVER +
"/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0b3IiXX0.IJehwM8tPVQFzJ2fZMBHveii1DRChVtzo7PEnSmmFt8",
)
.waitForElementVisible("#menu");
//has editor JWT and boardname provided and match to the board in the url
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0bzp0ZXN0Ym9hcmQiXX0.-P6gjYlPP5I2zgSoVTlADdesVPfSXV-JXZQK5uh3Xwo').waitForElementVisible('#menu');
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0bzp0ZXN0Ym9hcmQiXX0.-P6gjYlPP5I2zgSoVTlADdesVPfSXV-JXZQK5uh3Xwo').waitForElementNotPresent('#toolID-Clear');
browser
.url(
SERVER +
"/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0bzp0ZXN0Ym9hcmQiXX0.-P6gjYlPP5I2zgSoVTlADdesVPfSXV-JXZQK5uh3Xwo",
)
.waitForElementVisible("#menu");
browser
.url(
SERVER +
"/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0bzp0ZXN0Ym9hcmQiXX0.-P6gjYlPP5I2zgSoVTlADdesVPfSXV-JXZQK5uh3Xwo",
)
.waitForElementNotPresent("#toolID-Clear");
//has editor JWT and boardname provided and and not match to the board in the url
browser.url(SERVER + '/boards/testboard123?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0bzp0ZXN0Ym9hcmQiXX0.-P6gjYlPP5I2zgSoVTlADdesVPfSXV-JXZQK5uh3Xwo').waitForElementNotPresent('#menu');
browser
.url(
SERVER +
"/boards/testboard123?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJlZGl0bzp0ZXN0Ym9hcmQiXX0.-P6gjYlPP5I2zgSoVTlADdesVPfSXV-JXZQK5uh3Xwo",
)
.waitForElementNotPresent("#menu");
//is moderator and boardname contains ":"
browser.url(SERVER + '/boards/test:board?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdDpib2FyZCJdfQ.LKYcDccheD2oXAMAemxSekDeowGsMl29CFkgJgwbkGE').waitForElementNotPresent('#menu');
browser.url(SERVER + '/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdDpib2FyZCJdfQ.LKYcDccheD2oXAMAemxSekDeowGsMl29CFkgJgwbkGE').waitForElementNotPresent('#menu');
browser
.url(
SERVER +
"/boards/test:board?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdDpib2FyZCJdfQ.LKYcDccheD2oXAMAemxSekDeowGsMl29CFkgJgwbkGE",
)
.waitForElementNotPresent("#menu");
browser
.url(
SERVER +
"/boards/testboard?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJtb2RlcmF0b3I6dGVzdDpib2FyZCJdfQ.LKYcDccheD2oXAMAemxSekDeowGsMl29CFkgJgwbkGE",
)
.waitForElementNotPresent("#menu");
}
page.end();
}