diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..b33f5f6e --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +node_modules +.eslintrc.js +.DS_Store +package.json +package-lock.json \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..89ad6950 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,27 @@ +module.exports = { + "env": { + "commonjs": true, + "browser": true, + "es2021": true + }, + "extends": "eslint:recommended", + "overrides": [ + { + "env": { + "node": true + }, + "files": [ + ".eslintrc.{js,cjs}" + ], + "parserOptions": { + "sourceType": "script" + } + } + ], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + } +} diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 76f957b9..920996ed 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -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: @@ -19,13 +18,13 @@ jobs: node-version: [16.x, 18.x, 20.x] steps: - - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - - run: npm ci - - run: npm run build --if-present - - run: npm test - env: - CI: true + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm run build --if-present + - run: npm test + env: + CI: true diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 449ba6a1..9ad6f8f8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,7 +1,7 @@ name: deploy on: push: - tags: + tags: - v* jobs: @@ -9,15 +9,15 @@ jobs: name: deploy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Dokku - uses: dokku/github-action@bbb8818f9bad88edd3099a2399ef9be366754ff9 - with: - git_remote_url: ssh://dokku@${{ secrets.SSH_SERVER }}:22/wbo - ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} - git_push_flags: --force + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Dokku + uses: dokku/github-action@bbb8818f9bad88edd3099a2399ef9be366754ff9 + with: + git_remote_url: ssh://dokku@${{ secrets.SSH_SERVER }}:22/wbo + ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} + git_push_flags: --force push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..0408b135 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +package.json +package-lock.json +node_modules/ +.DS_Store +*.html \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..1edcaf73 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,12 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "embeddedLanguageFormatting": "auto", + "endOfLine": "lf", + "insertPragma": false, + "proseWrap": "preserve", + "requirePragma": false, + "tabWidth": 2, + "useTabs": false, + "printWidth": 80 +} diff --git a/README.md b/README.md index 717c7f18..61eb5506 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ If you have your own web server, and want to run a private instance of WBO on it ### Running the code in a container (safer) -If you use the [docker](https://www.docker.com/) containerization service, you can easily run WBO as a container. +If you use the [docker](https://www.docker.com/) containerization service, you can easily run WBO as a container. An official docker image for WBO is hosted on dockerhub as [`lovasoa/wbo`](https://hub.docker.com/r/lovasoa/wbo): [![WBO 1M docker pulls](https://img.shields.io/docker/pulls/lovasoa/wbo?style=flat)](https://hub.docker.com/repository/docker/lovasoa/wbo). You can run the following bash command to launch WBO on port 5001, while persisting the boards outside of docker: @@ -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 @@ -76,7 +80,7 @@ See instructions on our Wiki about [how to setup a reverse proxy for WBO](https: ## Translations -WBO is available in multiple languages. The translations are stored in [`server/translations.json`](./server/translations.json). +WBO is available in multiple languages. The translations are stored in [`server/translations.json`](./server/translations.json). If you feel like contributing to this collaborative project, you can [translate WBO into your own language](https://github.com/lovasoa/whitebophir/wiki/How-to-translate-WBO-into-your-own-language). ## Authentication @@ -86,10 +90,10 @@ WBO supports authentication using [Json Web Tokens](https://jwt.io/introduction) The `AUTH_SECRET_KEY` variable in [`configuration.js`](./server/configuration.js) should be filled with the secret key for the JWT. Within the payload, you can declare the user's roles as an array. -Currently the only accepted roles are `moderator` and `editor`. +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:","moderator:","editor:","editor:"] + "roles": [ + "moderator:", + "moderator:", + "editor:", + "editor:" + ] } ``` + 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). diff --git a/client-data/board.css b/client-data/board.css index 4f5e35a0..901a297c 100644 --- a/client-data/board.css +++ b/client-data/board.css @@ -1,286 +1,289 @@ -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 { - transform-origin: 0 0; + transform-origin: 0 0; } #loadingMessage { - font-size: 1.5em; - background: #eee linear-gradient(#eeeeee, #cccccc); - padding: 20px; - width: 40%; - line-height: 50px; - text-align: center; - border-radius: 10px; - position:fixed; - top: 40%; - left: 30%; - z-index: 1; - box-shadow: 0 0 2px #333333; - transition: 1s; + font-size: 1.5em; + background: #eee linear-gradient(#eeeeee, #cccccc); + padding: 20px; + width: 40%; + line-height: 50px; + text-align: center; + border-radius: 10px; + position: fixed; + top: 40%; + left: 30%; + z-index: 1; + box-shadow: 0 0 2px #333333; + transition: 1s; } #loadingMessage.hidden { - display: none; - opacity: 0; - z-index: -1; + display: none; + opacity: 0; + z-index: -1; } #loadingMessage::after { - content: "..."; + content: "..."; } /* Hide scrollbar for Chrome, Safari and Opera */ #menu::-webkit-scrollbar { - display: none; + display: none; } #menu { - -ms-overflow-style: none; - scrollbar-width: none; - font-size: 16px; - border-radius: 0; - overflow-y: scroll; - position: fixed; - margin-bottom: 30px; - left: 0; - top: 0; - color: black; - max-height: 100%; - transition-duration: 1s; - cursor: default; - padding: 10px; + -ms-overflow-style: none; + scrollbar-width: none; + font-size: 16px; + border-radius: 0; + overflow-y: scroll; + position: fixed; + margin-bottom: 30px; + left: 0; + top: 0; + color: black; + max-height: 100%; + transition-duration: 1s; + cursor: default; + padding: 10px; } #menu.closed { - 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")*/ - display: none; - font-size:4vh; - text-align: center; - letter-spacing:.5vw; - text-shadow: 0px 0px 5px white; - color:black; - padding:0; - margin:0; + 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")*/ + display: none; + font-size: 4vh; + text-align: center; + letter-spacing: 0.5vw; + text-shadow: 0px 0px 5px white; + color: black; + padding: 0; + margin: 0; } #menu .tools { - list-style-type:none; - padding:0; + list-style-type: none; + padding: 0; } #settings { - margin-bottom: 20px; + margin-bottom: 20px; } #menu .tool { - position: relative; - -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Safari */ - -khtml-user-select: none; /* Konqueror HTML */ - -moz-user-select: none; /* Old versions of Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ - user-select: none; /* Non-prefixed version, currently + position: relative; + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Old versions of Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently 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; - background: #ffffff; - margin-top: 10px; - height: 40px; - line-height: 40px; - border-radius: 0px; - max-width: 40px; - transition-duration: .2s; - overflow: hidden; - width: max-content; - box-shadow: inset 0 0 3px #8FA2BC; + pointer-events: auto; + white-space: nowrap; + 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: 0.2s; + overflow: hidden; + width: max-content; + box-shadow: inset 0 0 3px #8fa2bc; } #menu .tool:hover { - max-width: 100%; + max-width: 100%; } @media (hover: none), (pointer: coarse) { - #menu .tool:hover { - max-width: 40px; - } + #menu .tool:hover { + max-width: 40px; + } - #menu .tool:focus { - max-width: 100%; - } + #menu .tool:focus { + max-width: 100%; + } - #menu { - pointer-events: auto; - } - - #menu:focus-within { - pointer-events: none; - } + #menu { + pointer-events: auto; + } + #menu:focus-within { + pointer-events: none; + } } #menu .oneTouch:active { - border-radius: 3px; - background-color:#eeeeff; + border-radius: 3px; + background-color: #eeeeff; } #menu .tool:active { - box-shadow: inset 0 0 1px #ddeeff; - background-color:#eeeeff; + box-shadow: inset 0 0 1px #ddeeff; + 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; - width: 35px; - height: 35px; - margin: 2.5px; - font-family: mono, monospace; - overflow: hidden; + display: inline-block; + text-align: center; + width: 35px; + height: 35px; + margin: 2.5px; + font-family: mono, monospace; + overflow: hidden; } #menu img.tool-icon { - pointer-events: none; + pointer-events: none; } #menu .tool-icon > * { - display: block; - margin: auto; + display: block; + margin: auto; } #menu .tool-name { - text-align: center; - font-size: 23px; - margin-right: 20px; - margin-left: 20px; - margin-bottom: 2.5px; - display: inline-block; - vertical-align: text-bottom; + text-align: center; + font-size: 23px; + margin-right: 20px; + margin-left: 20px; + margin-bottom: 2.5px; + display: inline-block; + vertical-align: text-bottom; } #menu .tool-name.slider { - display: inline-block; - width: 150px; - height: 30px; - font-size: .9em; - line-height: 15px; - vertical-align: top; - padding: 6px; - + display: inline-block; + width: 150px; + height: 30px; + 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{ - display: none; +#menu .tool .tool-icon.secondaryIcon { + display: none; } -#menu .tool.hasSecondary .tool-icon.secondaryIcon{ - display: block; - position: absolute; - bottom: 0px; - left: 26px; - width: 12px; - height: 12px; +#menu .tool.hasSecondary .tool-icon.secondaryIcon { + display: block; + position: absolute; + bottom: 0px; + left: 26px; + width: 12px; + height: 12px; } input { - font-size:16px; + font-size: 16px; } #chooseColor { - width: 100%; - height:100%; - border: 0; - border-radius: 0; - color:black; - display: block; - margin: 0; - padding: 0; + width: 100%; + height: 100%; + border: 0; + border-radius: 0; + color: black; + display: block; + margin: 0; + padding: 0; } .colorPresets { - margin-right: 20px; - vertical-align: top; - display: inline-block; + margin-right: 20px; + vertical-align: top; + display: inline-block; } .colorPresetButton { - width: 30px; - height: 30px; - border: 1px solid black; - border-radius: 3px; - display: inline-block; - margin-right: 6px; - padding: 0; - vertical-align: middle; + width: 30px; + height: 30px; + border: 1px solid black; + border-radius: 3px; + display: inline-block; + margin-right: 6px; + padding: 0; + vertical-align: middle; } .rangeChooser { - display: block; - border: 0; - width: 100%; - margin: 0; - background: transparent; + display: block; + border: 0; + width: 100%; + margin: 0; + background: transparent; } line { - fill: none; - stroke-linecap: round; - stroke-linejoin: round; + fill: none; + stroke-linecap: round; + stroke-linejoin: round; } path { - fill: none; - stroke-linecap: round; - stroke-linejoin: round; + fill: none; + stroke-linecap: round; + stroke-linejoin: round; } 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; + pointer-events: none; + transition: 0.1s; } #cursor-me { - transition: 0s; + transition: 0s; } /* Internet Explorer specific CSS */ @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { - #chooseColor { - color: transparent; - } - label.tool-name[for=chooseColor] { - line-height: 10px; - } + #chooseColor { + color: transparent; + } + label.tool-name[for="chooseColor"] { + line-height: 10px; + } } diff --git a/client-data/index.css b/client-data/index.css index 00e4e0c7..7ab571d4 100644 --- a/client-data/index.css +++ b/client-data/index.css @@ -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 { diff --git a/client-data/js/board.js b/client-data/js/board.js index 547f03ca..2d8c900d 100644 --- a/client-data/js/board.js +++ b/client-data/js/board.js @@ -27,13 +27,13 @@ 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, '_'); - return translations[key] || s; - } - }; + var translations = JSON.parse(document.getElementById("translations").text); + return { + t: function translate(s) { + var key = s.toLowerCase().replace(/ /g, "_"); + return translations[key] || s; + }, + }; })(); Tools.server_config = JSON.parse(document.getElementById("configuration").text); @@ -53,308 +53,332 @@ Tools.isIE = /MSIE|Trident/.test(window.navigator.userAgent); Tools.socket = null; Tools.connect = function () { - var self = this; - - // Destroy socket if one already exists - if (self.socket) { - self.socket.destroy(); - delete self.socket; - self.socket = null; - } - - var url = new URL(window.location); - 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")) { - socket_params.query = "token=" + params.get("token"); - } - - this.socket = io.connect('', socket_params); - - //Receive draw instructions from the server - this.socket.on("broadcast", function (msg) { - handleMessage(msg).finally(function afterload() { - var loadingEl = document.getElementById("loadingMessage"); - loadingEl.classList.add("hidden"); - }); - }); - - this.socket.on("reconnect", function onReconnection() { - Tools.socket.emit('joinboard', Tools.boardName); - }); + var self = this; + + // Destroy socket if one already exists + if (self.socket) { + self.socket.destroy(); + delete self.socket; + self.socket = null; + } + + var url = new URL(window.location); + 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")) { + socket_params.query = "token=" + params.get("token"); + } + + this.socket = io.connect("", socket_params); + + //Receive draw instructions from the server + this.socket.on("broadcast", function (msg) { + handleMessage(msg).finally(function afterload() { + var loadingEl = document.getElementById("loadingMessage"); + loadingEl.classList.add("hidden"); + }); + }); + + this.socket.on("reconnect", function onReconnection() { + Tools.socket.emit("joinboard", Tools.boardName); + }); }; Tools.connect(); Tools.boardName = (function () { - var path = window.location.pathname.split("/"); - return decodeURIComponent(path[path.length - 1]); + var path = window.location.pathname.split("/"); + return decodeURIComponent(path[path.length - 1]); })(); -Tools.token = (function() { - var url = new URL(window.location); - var params = new URLSearchParams(url.search); - return params.get("token"); +Tools.token = (function () { + var url = new URL(window.location); + var params = new URLSearchParams(url.search); + return params.get("token"); })(); //Get the board as soon as the page is loaded Tools.socket.emit("getboard", Tools.boardName); function saveBoardNametoLocalStorage() { - var boardName = Tools.boardName; - 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) { - // On localstorage or json error, reset board list - recentBoards = []; - console.log("Board history loading error", e); - } - recentBoards = recentBoards.filter(function (name) { - return name !== boardName; - }); - recentBoards.unshift(boardName); - recentBoards = recentBoards.slice(0, 20); - localStorage.setItem(key, JSON.stringify(recentBoards)); + var boardName = Tools.boardName; + 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) { + // On localstorage or json error, reset board list + recentBoards = []; + console.log("Board history loading error", e); + } + recentBoards = recentBoards.filter(function (name) { + return name !== boardName; + }); + recentBoards.unshift(boardName); + recentBoards = recentBoards.slice(0, 20); + localStorage.setItem(key, JSON.stringify(recentBoards)); } // Refresh recent boards list on each page show window.addEventListener("pageshow", saveBoardNametoLocalStorage); Tools.HTML = { - template: new Minitpl("#tools > .tool"), - addShortcut: function addShortcut(key, callback) { - window.addEventListener("keydown", function (e) { - if (e.key === key && !e.target.matches("input[type=text], textarea")) { - callback(); - } - }); - }, - addTool: function (toolName, toolIcon, toolIconHTML, toolShortcut, oneTouch) { - var callback = function () { - Tools.change(toolName); - }; - this.addShortcut(toolShortcut, function () { - Tools.change(toolName); - document.activeElement.blur && document.activeElement.blur(); - }); - return this.template.add(function (elem) { - elem.addEventListener("click", callback); - elem.id = "toolID-" + 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") + "]" : ""); - if (Tools.list[toolName].secondary) { - elem.classList.add('hasSecondary'); - var secondaryIcon = elem.getElementsByClassName('secondaryIcon')[0]; - secondaryIcon.src = Tools.list[toolName].secondary.icon; - toolIconElem.classList.add("primaryIcon"); - } - }); - }, - changeTool: function (oldToolName, newToolName) { - var oldTool = document.getElementById("toolID-" + oldToolName); - var newTool = document.getElementById("toolID-" + newToolName); - if (oldTool) oldTool.classList.remove("curTool"); - if (newTool) newTool.classList.add("curTool"); - }, - toggle: function (toolName, name, icon) { - var elem = document.getElementById("toolID-" + toolName); - - // Change secondary icon - var primaryIcon = elem.getElementsByClassName("primaryIcon")[0]; - var secondaryIcon = elem.getElementsByClassName("secondaryIcon")[0]; - var primaryIconSrc = primaryIcon.src; - var secondaryIconSrc = secondaryIcon.src; - primaryIcon.src = secondaryIconSrc; - secondaryIcon.src = primaryIconSrc; - - // Change primary icon - elem.getElementsByClassName("tool-icon")[0].src = icon; - elem.getElementsByClassName("tool-name")[0].textContent = Tools.i18n.t(name); - }, - addStylesheet: function (href) { - //Adds a css stylesheet to the html or svg document - var link = document.createElement("link"); - link.href = href; - link.rel = "stylesheet"; - link.type = "text/css"; - document.head.appendChild(link); - }, - colorPresetTemplate: new Minitpl("#colorPresetSel .colorPresetButton"), - addColorButton: function (button) { - var setColor = Tools.setColor.bind(Tools, button.color); - 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.style.backgroundColor = button.color; - if (button.key) { - elem.title = Tools.i18n.t("keyboard shortcut") + ": " + button.key; - } - }); - } + template: new Minitpl("#tools > .tool"), + addShortcut: function addShortcut(key, callback) { + window.addEventListener("keydown", function (e) { + if (e.key === key && !e.target.matches("input[type=text], textarea")) { + callback(); + } + }); + }, + addTool: function (toolName, toolIcon, toolIconHTML, toolShortcut, oneTouch) { + var callback = function () { + Tools.change(toolName); + }; + this.addShortcut(toolShortcut, function () { + Tools.change(toolName); + document.activeElement.blur && document.activeElement.blur(); + }); + return this.template.add(function (elem) { + elem.addEventListener("click", callback); + elem.id = "toolID-" + 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") + "]" + : ""); + if (Tools.list[toolName].secondary) { + elem.classList.add("hasSecondary"); + var secondaryIcon = elem.getElementsByClassName("secondaryIcon")[0]; + secondaryIcon.src = Tools.list[toolName].secondary.icon; + toolIconElem.classList.add("primaryIcon"); + } + }); + }, + changeTool: function (oldToolName, newToolName) { + var oldTool = document.getElementById("toolID-" + oldToolName); + var newTool = document.getElementById("toolID-" + newToolName); + if (oldTool) oldTool.classList.remove("curTool"); + if (newTool) newTool.classList.add("curTool"); + }, + toggle: function (toolName, name, icon) { + var elem = document.getElementById("toolID-" + toolName); + + // Change secondary icon + var primaryIcon = elem.getElementsByClassName("primaryIcon")[0]; + var secondaryIcon = elem.getElementsByClassName("secondaryIcon")[0]; + var primaryIconSrc = primaryIcon.src; + var secondaryIconSrc = secondaryIcon.src; + primaryIcon.src = secondaryIconSrc; + secondaryIcon.src = primaryIconSrc; + + // Change primary icon + elem.getElementsByClassName("tool-icon")[0].src = icon; + elem.getElementsByClassName("tool-name")[0].textContent = + Tools.i18n.t(name); + }, + addStylesheet: function (href) { + //Adds a css stylesheet to the html or svg document + var link = document.createElement("link"); + link.href = href; + link.rel = "stylesheet"; + link.type = "text/css"; + document.head.appendChild(link); + }, + colorPresetTemplate: new Minitpl("#colorPresetSel .colorPresetButton"), + addColorButton: function (button) { + var setColor = Tools.setColor.bind(Tools, button.color); + 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.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"); - return Tools.server_config.BLOCKED_TOOLS.includes(tool.name); + if (tool.name.includes(",")) + throw new Error("Tool Names must not contain a comma"); + return Tools.server_config.BLOCKED_TOOLS.includes(tool.name); }; /** * Register a new tool, without touching the User Interface */ 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..."); - } - - //Format the new tool correctly - Tools.applyHooks(Tools.toolHooks, newTool); - - //Add the tool to the list - Tools.list[newTool.name] = newTool; - - // Register the change handlers - if (newTool.onSizeChange) Tools.sizeChangeHandlers.push(newTool.onSizeChange); - - //There may be pending messages for the tool - var pending = Tools.pendingMessages[newTool.name]; - if (pending) { - console.log("Drawing pending messages for '%s'.", newTool.name); - var msg; - while (msg = pending.shift()) { - //Transmit the message to the tool (precising that it comes from the network) - newTool.draw(msg, false); - } - } + 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...", + ); + } + + //Format the new tool correctly + Tools.applyHooks(Tools.toolHooks, newTool); + + //Add the tool to the list + Tools.list[newTool.name] = newTool; + + // Register the change handlers + if (newTool.onSizeChange) Tools.sizeChangeHandlers.push(newTool.onSizeChange); + + //There may be pending messages for the tool + var pending = Tools.pendingMessages[newTool.name]; + if (pending) { + console.log("Drawing pending messages for '%s'.", newTool.name); + var msg; + while ((msg = pending.shift())) { + //Transmit the message to the tool (precising that it comes from the network) + newTool.draw(msg, false); + } + } }; /** * Add a new tool to the user interface */ Tools.add = function (newTool) { - if (Tools.isBlocked(newTool)) return; + if (Tools.isBlocked(newTool)) return; + + Tools.register(newTool); + + if (newTool.stylesheet) { + Tools.HTML.addStylesheet(newTool.stylesheet); + } + + //Add the tool to the GUI + Tools.HTML.addTool( + newTool.name, + newTool.icon, + newTool.iconHTML, + newTool.shortcut, + newTool.oneTouch, + ); +}; - Tools.register(newTool); +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 === oldTool) { + if (newTool.secondary) { + newTool.secondary.active = !newTool.secondary.active; + var props = newTool.secondary.active ? newTool.secondary : newTool; + Tools.HTML.toggle(newTool.name, props.name, props.icon); + if (newTool.secondary.switch) newTool.secondary.switch(); + } + return; + } + if (!newTool.oneTouch) { + //Update the GUI + var curToolName = Tools.curTool ? Tools.curTool.name : ""; + try { + Tools.HTML.changeTool(curToolName, toolName); + } catch (e) { + console.error("Unable to update the GUI with the new tool. " + e); + } + Tools.svg.style.cursor = newTool.mouseCursor || "auto"; + Tools.board.title = Tools.i18n.t(newTool.helpText || ""); - if (newTool.stylesheet) { - Tools.HTML.addStylesheet(newTool.stylesheet); - } + //There is not necessarily already a curTool + if (Tools.curTool !== null) { + //It's useless to do anything if the new tool is already selected + if (newTool === Tools.curTool) return; - //Add the tool to the GUI - Tools.HTML.addTool(newTool.name, newTool.icon, newTool.iconHTML, newTool.shortcut, newTool.oneTouch); -}; + //Remove the old event listeners + Tools.removeToolListeners(Tools.curTool); -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 === oldTool) { - if (newTool.secondary) { - newTool.secondary.active = !newTool.secondary.active; - var props = newTool.secondary.active ? newTool.secondary : newTool; - Tools.HTML.toggle(newTool.name, props.name, props.icon); - if (newTool.secondary.switch) newTool.secondary.switch(); - } - return; - } - if (!newTool.oneTouch) { - //Update the GUI - var curToolName = (Tools.curTool) ? Tools.curTool.name : ""; - try { - Tools.HTML.changeTool(curToolName, toolName); - } catch (e) { - console.error("Unable to update the GUI with the new tool. " + e); - } - Tools.svg.style.cursor = newTool.mouseCursor || "auto"; - Tools.board.title = Tools.i18n.t(newTool.helpText || ""); - - //There is not necessarily already a curTool - if (Tools.curTool !== null) { - //It's useless to do anything if the new tool is already selected - if (newTool === Tools.curTool) return; - - //Remove the old event listeners - Tools.removeToolListeners(Tools.curTool); - - //Call the callbacks of the old tool - Tools.curTool.onquit(newTool); - } - - //Add the new event listeners - Tools.addToolListeners(newTool); - Tools.curTool = newTool; - } - - //Call the start callback of the new tool - newTool.onstart(oldTool); + //Call the callbacks of the old tool + Tools.curTool.onquit(newTool); + } + + //Add the new event listeners + Tools.addToolListeners(newTool); + Tools.curTool = newTool; + } + + //Call the start callback of the new tool + newTool.onstart(oldTool); }; 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 }); - } + for (var event in tool.compiledListeners) { + var listener = tool.compiledListeners[event]; + var target = listener.target || Tools.board; + target.addEventListener(event, listener, { passive: false }); + } }; Tools.removeToolListeners = function removeToolListeners(tool) { - for (var event in tool.compiledListeners) { - var listener = tool.compiledListeners[event]; - var target = listener.target || Tools.board; - target.removeEventListener(event, listener); - // also attempt to remove with capture = true in IE - if (Tools.isIE) target.removeEventListener(event, listener, true); - } + for (var event in tool.compiledListeners) { + var listener = tool.compiledListeners[event]; + var target = listener.target || Tools.board; + target.removeEventListener(event, listener); + // also attempt to remove with capture = true in IE + if (Tools.isIE) target.removeEventListener(event, listener, true); + } }; (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) { - Tools.change(Tools.curTool.name); - } - } - window.addEventListener("keydown", handleShift.bind(null, true)); - window.addEventListener("keyup", handleShift.bind(null, false)); + // 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 + ) { + Tools.change(Tools.curTool.name); + } + } + window.addEventListener("keydown", handleShift.bind(null, true)); + window.addEventListener("keyup", handleShift.bind(null, false)); })(); Tools.send = function (data, toolName) { - toolName = toolName || Tools.curTool.name; - var d = data; - d.tool = toolName; - Tools.applyHooks(Tools.messageHooks, d); - var message = { - "board": Tools.boardName, - "data": d - }; - Tools.socket.emit('broadcast', message); + toolName = toolName || Tools.curTool.name; + var d = data; + d.tool = toolName; + Tools.applyHooks(Tools.messageHooks, d); + var message = { + board: Tools.boardName, + data: d, + }; + Tools.socket.emit("broadcast", message); }; Tools.drawAndSend = function (data, tool) { - if (tool == null) tool = Tools.curTool; - tool.draw(data, true); - Tools.send(data, tool.name); + if (tool == null) tool = Tools.curTool; + tool.draw(data, true); + Tools.send(data, tool.name); }; //Object containing the messages that have been received before the corresponding tool @@ -363,121 +387,133 @@ Tools.pendingMessages = {}; // Send a message to the corresponding tool function messageForTool(message) { - var name = message.tool, - tool = Tools.list[name]; - - if (tool) { - Tools.applyHooks(Tools.messageHooks, message); - tool.draw(message, false); - } else { - ///We received a message destinated to a tool that we don't have - //So we add it to the pending messages - if (!Tools.pendingMessages[name]) Tools.pendingMessages[name] = [message]; - else Tools.pendingMessages[name].push(message); - } - - 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}); - } + var name = message.tool, + tool = Tools.list[name]; + + if (tool) { + Tools.applyHooks(Tools.messageHooks, message); + tool.draw(message, false); + } else { + ///We received a message destinated to a tool that we don't have + //So we add it to the pending messages + if (!Tools.pendingMessages[name]) Tools.pendingMessages[name] = [message]; + else Tools.pendingMessages[name].push(message); + } + + 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, + }); + } } // Apply the function to all arguments by batches function batchCall(fn, args) { - var BATCH_SIZE = 1024; - if (args.length === 0) { - return Promise.resolve(); - } else { - var batch = args.slice(0, BATCH_SIZE); - var rest = args.slice(BATCH_SIZE); - return Promise.all(batch.map(fn)) - .then(function () { - return new Promise(requestAnimationFrame); - }).then(batchCall.bind(null, fn, rest)); - } + var BATCH_SIZE = 1024; + if (args.length === 0) { + return Promise.resolve(); + } else { + var batch = args.slice(0, BATCH_SIZE); + var rest = args.slice(BATCH_SIZE); + return Promise.all(batch.map(fn)) + .then(function () { + return new Promise(requestAnimationFrame); + }) + .then(batchCall.bind(null, fn, rest)); + } } // Call messageForTool recursively on the message and its children function handleMessage(message) { - //Check if the message is in the expected format - if (!message.tool && !message._children) { - console.error("Received a badly formatted message (no tool). ", message); - } - if (message.tool) messageForTool(message); - if (message._children) return batchCall(handleMessage, message._children); - else return Promise.resolve(); + //Check if the message is in the expected format + if (!message.tool && !message._children) { + console.error("Received a badly formatted message (no tool). ", message); + } + if (message.tool) messageForTool(message); + if (message._children) return batchCall(handleMessage, message._children); + else return Promise.resolve(); } Tools.unreadMessagesCount = 0; Tools.newUnreadMessage = function () { - Tools.unreadMessagesCount++; - updateDocumentTitle(); + Tools.unreadMessagesCount++; + updateDocumentTitle(); }; window.addEventListener("focus", function () { - Tools.unreadMessagesCount = 0; - updateDocumentTitle(); + Tools.unreadMessagesCount = 0; + updateDocumentTitle(); }); function updateDocumentTitle() { - document.title = - (Tools.unreadMessagesCount ? '(' + Tools.unreadMessagesCount + ') ' : '') + - Tools.boardName + - " | WBO"; + document.title = + (Tools.unreadMessagesCount ? "(" + Tools.unreadMessagesCount + ") " : "") + + Tools.boardName + + " | WBO"; } (function () { - // Scroll and hash handling - var scrollTimeout, lastStateUpdate = Date.now(); - - window.addEventListener("scroll", function onScroll() { - var scale = Tools.getScale(); - var x = document.documentElement.scrollLeft / scale, - y = document.documentElement.scrollTop / scale; - - 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) { - window.history.pushState({}, "", hash); - lastStateUpdate = Date.now(); - } else { - window.history.replaceState({}, "", hash); - } - }, 100); - }); - - function setScrollFromHash() { - var coords = window.location.hash.slice(1).split(','); - var x = coords[0] | 0; - var y = coords[1] | 0; - var scale = parseFloat(coords[2]); - resizeCanvas({ x: x, y: y }); - Tools.setScale(scale); - window.scrollTo(x * scale, y * scale); - } - - window.addEventListener("hashchange", setScrollFromHash, false); - window.addEventListener("popstate", setScrollFromHash, false); - window.addEventListener("DOMContentLoaded", setScrollFromHash, false); + // Scroll and hash handling + var scrollTimeout, + lastStateUpdate = Date.now(); + + window.addEventListener("scroll", function onScroll() { + var scale = Tools.getScale(); + var x = document.documentElement.scrollLeft / scale, + y = document.documentElement.scrollTop / scale; + + 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 + ) { + window.history.pushState({}, "", hash); + lastStateUpdate = Date.now(); + } else { + window.history.replaceState({}, "", hash); + } + }, 100); + }); + + function setScrollFromHash() { + var coords = window.location.hash.slice(1).split(","); + var x = coords[0] | 0; + var y = coords[1] | 0; + var scale = parseFloat(coords[2]); + resizeCanvas({ x: x, y: y }); + Tools.setScale(scale); + window.scrollTo(x * scale, y * scale); + } + + window.addEventListener("hashchange", setScrollFromHash, false); + window.addEventListener("popstate", setScrollFromHash, false); + window.addEventListener("DOMContentLoaded", setScrollFromHash, false); })(); function resizeCanvas(m) { - //Enlarge the canvas whenever something is drawn near its border - 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); - } - if (y > Tools.svg.height.baseVal.value - 2000) { - Tools.svg.height.baseVal.value = Math.min(y + 2000, MAX_BOARD_SIZE); - } + //Enlarge the canvas whenever something is drawn near its border + 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); + } + if (y > Tools.svg.height.baseVal.value - 2000) { + Tools.svg.height.baseVal.value = Math.min(y + 2000, MAX_BOARD_SIZE); + } } function updateUnreadCount(m) { - if (document.hidden && ["child", "update"].indexOf(m.type) === -1) { - Tools.newUnreadMessage(); - } + if (document.hidden && ["child", "update"].indexOf(m.type) === -1) { + Tools.newUnreadMessage(); + } } // List of hook functions that will be applied to messages before sending or drawing them @@ -486,194 +522,211 @@ 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 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 + ')'; - clearTimeout(scaleTimeout); - scaleTimeout = setTimeout(function () { - Tools.svg.style.willChange = 'auto'; - }, 1000); - Tools.scale = scale; - return scale; -} + 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 + ")"; + clearTimeout(scaleTimeout); + scaleTimeout = setTimeout(function () { + Tools.svg.style.willChange = "auto"; + }, 1000); + Tools.scale = scale; + return scale; +}; Tools.getScale = function getScale() { - return Tools.scale; -} + 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") { - tool.listeners = {}; - } - if (typeof (tool.onstart) !== "function") { - tool.onstart = function () { }; - } - if (typeof (tool.onquit) !== "function") { - tool.onquit = function () { }; - } - }, - function compileListeners(tool) { - //compile listeners into compiledListeners - var listeners = tool.listeners; - - //A tool may provide precompiled listeners - var compiled = tool.compiledListeners || {}; - tool.compiledListeners = compiled; - - 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) { - //Currently, we don't handle multitouch - if (evt.changedTouches.length === 1) { - //evt.preventDefault(); - var touch = evt.changedTouches[0]; - var x = touch.pageX / Tools.getScale(), - y = touch.pageY / Tools.getScale(); - 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 f(evt); - }); - } - - if (listeners.press) { - compiled["mousedown"] = wrapUnsetHover(compile(listeners.press), tool.name); - compiled["touchstart"] = wrapUnsetHover(compileTouch(listeners.press), tool.name); - } - if (listeners.move) { - compiled["mousemove"] = compile(listeners.move); - compiled["touchmove"] = compileTouch(listeners.move); - } - if (listeners.release) { - var release = compile(listeners.release), - releaseTouch = compileTouch(listeners.release); - compiled["mouseup"] = release; - if (!Tools.isIE) compiled["mouseleave"] = release; - compiled["touchleave"] = releaseTouch; - compiled["touchend"] = releaseTouch; - compiled["touchcancel"] = releaseTouch; - } - } + function checkToolAttributes(tool) { + 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.onquit !== "function") { + tool.onquit = function () {}; + } + }, + function compileListeners(tool) { + //compile listeners into compiledListeners + var listeners = tool.listeners; + + //A tool may provide precompiled listeners + var compiled = tool.compiledListeners || {}; + tool.compiledListeners = compiled; + + 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) { + //Currently, we don't handle multitouch + if (evt.changedTouches.length === 1) { + //evt.preventDefault(); + var touch = evt.changedTouches[0]; + var x = touch.pageX / Tools.getScale(), + y = touch.pageY / Tools.getScale(); + 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 f(evt); + }; + } + + if (listeners.press) { + compiled["mousedown"] = wrapUnsetHover( + compile(listeners.press), + tool.name, + ); + compiled["touchstart"] = wrapUnsetHover( + compileTouch(listeners.press), + tool.name, + ); + } + if (listeners.move) { + compiled["mousemove"] = compile(listeners.move); + compiled["touchmove"] = compileTouch(listeners.move); + } + if (listeners.release) { + var release = compile(listeners.release), + releaseTouch = compileTouch(listeners.release); + compiled["mouseup"] = release; + if (!Tools.isIE) compiled["mouseleave"] = release; + compiled["touchleave"] = releaseTouch; + compiled["touchend"] = releaseTouch; + compiled["touchcancel"] = releaseTouch; + } + }, ]; Tools.applyHooks = function (hooks, object) { - //Apply every hooks on the object - hooks.forEach(function (hook) { - hook(object); - }); + //Apply every hooks on the object + hooks.forEach(function (hook) { + hook(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 - if (prefix) uid = prefix + uid; - if (suffix) uid = uid + suffix; - return uid; + 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 + if (prefix) uid = prefix + uid; + if (suffix) uid = uid + suffix; + return uid; }; Tools.createSVGElement = function createSVGElement(name, attrs) { - var elem = document.createElementNS(Tools.svg.namespaceURI, name); - if (typeof (attrs) !== "object") return elem; - Object.keys(attrs).forEach(function (key, i) { - elem.setAttributeNS(null, key, attrs[key]); - }); - return elem; + var elem = document.createElementNS(Tools.svg.namespaceURI, name); + if (typeof attrs !== "object") return elem; + Object.keys(attrs).forEach(function (key, i) { + elem.setAttributeNS(null, key, attrs[key]); + }); + return elem; }; Tools.positionElement = function (elem, x, y) { - elem.style.top = y + "px"; - elem.style.left = x + "px"; + elem.style.top = y + "px"; + elem.style.left = x + "px"; }; 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"); Tools.setColor = function (color) { - Tools.color_chooser.value = color; + Tools.color_chooser.value = color; }; 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; }; + 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; + }; })(); Tools.colorPresets.forEach(Tools.HTML.addColorButton.bind(Tools.HTML)); Tools.sizeChangeHandlers = []; Tools.setSize = (function size() { - var chooser = document.getElementById("chooseSize"); - - function update() { - var size = Math.max(1, Math.min(50, chooser.value | 0)); - chooser.value = size; - Tools.sizeChangeHandlers.forEach(function (handler) { - handler(size); - }); - } - update(); - - chooser.onchange = chooser.oninput = update; - return function (value) { - if (value !== null && value !== undefined) { chooser.value = value; update(); } - return parseInt(chooser.value); - }; + var chooser = document.getElementById("chooseSize"); + + function update() { + var size = Math.max(1, Math.min(50, chooser.value | 0)); + chooser.value = size; + Tools.sizeChangeHandlers.forEach(function (handler) { + handler(size); + }); + } + update(); + + chooser.onchange = chooser.oninput = update; + return function (value) { + 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"); - var opacityIndicator = document.getElementById("opacityIndicator"); - - function update() { - opacityIndicator.setAttribute("opacity", chooser.value); - } - update(); - - chooser.onchange = chooser.oninput = update; - return function () { - return Math.max(0.1, Math.min(1, chooser.value)); - }; + var chooser = document.getElementById("chooseOpacity"); + var opacityIndicator = document.getElementById("opacityIndicator"); + + function update() { + opacityIndicator.setAttribute("opacity", chooser.value); + } + update(); + + chooser.onchange = chooser.oninput = update; + return function () { + return Math.max(0.1, Math.min(1, chooser.value)); + }; })(); - //Scale the canvas on load Tools.svg.width.baseVal.value = document.body.clientWidth; Tools.svg.height.baseVal.value = document.body.clientHeight; @@ -696,25 +749,24 @@ Tools.svg.height.baseVal.value = document.body.clientHeight; } */ - (function () { - var pos = {top: 0, scroll:0}; - var menu = document.getElementById("menu"); - function menu_mousedown(evt) { - pos = { - top: menu.scrollTop, - scroll: evt.clientY - } - menu.addEventListener("mousemove", menu_mousemove); - document.addEventListener("mouseup", menu_mouseup); - } - function menu_mousemove(evt) { - var dy = evt.clientY - pos.scroll; - menu.scrollTop = pos.top - dy; - } - function menu_mouseup(evt) { - menu.removeEventListener("mousemove", menu_mousemove); - document.removeEventListener("mouseup", menu_mouseup); - } - menu.addEventListener("mousedown", menu_mousedown); -})() + var pos = { top: 0, scroll: 0 }; + var menu = document.getElementById("menu"); + function menu_mousedown(evt) { + pos = { + top: menu.scrollTop, + scroll: evt.clientY, + }; + menu.addEventListener("mousemove", menu_mousemove); + document.addEventListener("mouseup", menu_mouseup); + } + function menu_mousemove(evt) { + var dy = evt.clientY - pos.scroll; + menu.scrollTop = pos.top - dy; + } + function menu_mouseup(evt) { + menu.removeEventListener("mousemove", menu_mousemove); + document.removeEventListener("mouseup", menu_mouseup); + } + menu.addEventListener("mousedown", menu_mousedown); +})(); diff --git a/client-data/js/canvascolor.js b/client-data/js/canvascolor.js index a1c99421..a3ccbd85 100644 --- a/client-data/js/canvascolor.js +++ b/client-data/js/canvascolor.js @@ -24,7 +24,6 @@ * @licend */ - /*jshint bitwise:false*/ // ==ClosureCompiler== @@ -35,208 +34,246 @@ // @use_types_for_optimization true // ==/ClosureCompiler== -var canvascolor = (function() {//Code Isolation - "use strict"; - - (function addCSS () { - var styleTag = document.createElement("style"); - styleTag.innerHTML = [".canvascolor-container{", - "background-color:black;", - "border-radius:5px;", - "overflow:hidden;", - "width:179px;", - "padding:2px;", - "display:none;", - "}", - ".canvascolor-container canvas{", - "cursor:crosshair;", - "}", - ".canvascolor-history{", - "overflow:auto;", - "}", - ".canvascolor-history > div{", - "margin:2px;", - "display:inline-block;", - "}"].join(""); - document.head.appendChild(styleTag); - })(); - - 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, - 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]; - } +var canvascolor = (function () { + //Code Isolation + "use strict"; + + (function addCSS() { + var styleTag = document.createElement("style"); + styleTag.innerHTML = [ + ".canvascolor-container{", + "background-color:black;", + "border-radius:5px;", + "overflow:hidden;", + "width:179px;", + "padding:2px;", + "display:none;", + "}", + ".canvascolor-container canvas{", + "cursor:crosshair;", + "}", + ".canvascolor-history{", + "overflow:auto;", + "}", + ".canvascolor-history > div{", + "margin:2px;", + "display:inline-block;", + "}", + ].join(""); + document.head.appendChild(styleTag); + })(); + + 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, + 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]; } - - function isFixedPosition(elem) { - do { - if (getComputedStyle(elem).position === "fixed") return true; - } while ( (elem = elem.parentElement) !== null ); - return false; + } + + function isFixedPosition(elem) { + do { + if (getComputedStyle(elem).position === "fixed") return true; + } while ((elem = elem.parentElement) !== null); + return false; + } + + var containerTemplate; + (function createContainer() { + containerTemplate = document.createElement("div"); + containerTemplate.className = "canvascolor-container"; + var canvas = document.createElement("canvas"); + var historyDiv = document.createElement("div"); + historyDiv.className = "canvascolor-history"; + containerTemplate.appendChild(canvas); + containerTemplate.appendChild(historyDiv); + })(); + + function canvascolor(elem) { + var curcolor = elem.value || "#000"; + + var w = 200, + h = w / 2; + + var container = containerTemplate.cloneNode(true); + 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; + + 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"; + previewdiv.style.borderRadius = "5px"; + + document.body.appendChild(container); + + function displayContainer() { + var rect = elem.getBoundingClientRect(); + 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.display = "block"; + } + function hideContainer() { + container.style.display = "none"; } - var containerTemplate; - (function createContainer(){ - containerTemplate = document.createElement("div"); - containerTemplate.className = "canvascolor-container"; - var canvas = document.createElement("canvas"); - var historyDiv = document.createElement("div"); - historyDiv.className = "canvascolor-history"; - containerTemplate.appendChild(canvas); - containerTemplate.appendChild(historyDiv); - })(); - - function canvascolor(elem) { - var curcolor = elem.value || "#000"; - - var w=200, h=w/2; - - var container = containerTemplate.cloneNode(true); - 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; - - 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"; - previewdiv.style.borderRadius = "5px"; - - document.body.appendChild(container); - - function displayContainer(){ - var rect = elem.getBoundingClientRect(); - 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.display = "block"; - } - function hideContainer(){ - container.style.display = "none"; - } - - elem.addEventListener("mouseover", displayContainer, true); - container.addEventListener("mouseleave", hideContainer, false); - elem.addEventListener("keyup", function(){ - changeColor(elem.value, true); - }, true); - + elem.addEventListener("mouseover", displayContainer, true); + container.addEventListener("mouseleave", hideContainer, false); + elem.addEventListener( + "keyup", + function () { changeColor(elem.value, true); + }, + true, + ); - 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]); - } + changeColor(elem.value, true); - function colorAt(coords) { - var x=coords[0], y=coords[1]; - return hsv2rgb(x/w*Math.PI, 1, (1-y/h)*255); - } + var idata = ctx.createImageData(w, h); - function render() { - for (var x=0; x= 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) { + 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) { + return pointInTransformedBBox(corner, bbox_a); + }); + } - 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) { - 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]; + })(); } diff --git a/client-data/js/minitpl.js b/client-data/js/minitpl.js index 88f476a3..0a32af9c 100644 --- a/client-data/js/minitpl.js +++ b/client-data/js/minitpl.js @@ -1,7 +1,7 @@ /** * MINITPL ********************************************************* - * @licstart The following is the entire license notice for the + * @licstart The following is the entire license notice for the * JavaScript code in this page. * * Copyright (C) 2013 Ophir LOJKINE @@ -25,40 +25,38 @@ */ Minitpl = (function () { + function Minitpl(elem, data) { + this.elem = typeof elem === "string" ? document.querySelector(elem) : elem; + if (!elem) { + throw "Invalid element!"; + } + this.parent = this.elem.parentNode; + this.parent.removeChild(this.elem); + } - function Minitpl(elem, data) { - this.elem = (typeof (elem) === "string") ? document.querySelector(elem) : elem; - if (!elem) { - throw "Invalid element!"; - } - this.parent = this.elem.parentNode; - this.parent.removeChild(this.elem); - } + function transform(element, transformer) { + if (typeof transformer === "function") { + transformer(element); + } else { + element.textContent = transformer; + } + } - function transform(element, transformer) { - if (typeof (transformer) === "function") { - transformer(element); - } else { - element.textContent = transformer; - } - } - - Minitpl.prototype.add = function (data) { - var newElem = this.elem.cloneNode(true); - if (typeof (data) === "object") { - for (var key in data) { - var matches = newElem.querySelectorAll(key); - for (var i = 0; i < matches.length; i++) { - transform(matches[i], data[key]); - } - } - } else { - transform(newElem, data); - } - this.parent.appendChild(newElem); - return newElem; - } - - return Minitpl; -}()); + Minitpl.prototype.add = function (data) { + var newElem = this.elem.cloneNode(true); + if (typeof data === "object") { + for (var key in data) { + var matches = newElem.querySelectorAll(key); + for (var i = 0; i < matches.length; i++) { + transform(matches[i], data[key]); + } + } + } else { + transform(newElem, data); + } + this.parent.appendChild(newElem); + return newElem; + }; + return Minitpl; +})(); diff --git a/client-data/js/path-data-polyfill.js b/client-data/js/path-data-polyfill.js index e584672b..fd38dff7 100644 --- a/client-data/js/path-data-polyfill.js +++ b/client-data/js/path-data-polyfill.js @@ -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; diff --git a/client-data/manifest.json b/client-data/manifest.json index a4f3d529..60ba9a45 100644 --- a/client-data/manifest.json +++ b/client-data/manifest.json @@ -1,25 +1,25 @@ { - "name": "WBO Online Whiteboard", - "short_name": "WBO", - "display": "standalone", - "background_color": "#8FA2BC", - "theme_color": "#C4DFFF", - "description": "A free and opensource online collaborative drawing tool.", - "icons": [ - { - "src": "favicon.svg", - "sizes": "1024x1024", - "type": "image/svg+xml" - }, - { - "src": "apple-touch-icon.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "favicon.ico", - "sizes": "16x16", - "type": "image/x-icon" - } - ] -} \ No newline at end of file + "name": "WBO Online Whiteboard", + "short_name": "WBO", + "display": "standalone", + "background_color": "#8FA2BC", + "theme_color": "#C4DFFF", + "description": "A free and opensource online collaborative drawing tool.", + "icons": [ + { + "src": "favicon.svg", + "sizes": "1024x1024", + "type": "image/svg+xml" + }, + { + "src": "apple-touch-icon.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "favicon.ico", + "sizes": "16x16", + "type": "image/x-icon" + } + ] +} diff --git a/client-data/tools/clear/clear.js b/client-data/tools/clear/clear.js index 3ac6f745..29c01118 100644 --- a/client-data/tools/clear/clear.js +++ b/client-data/tools/clear/clear.js @@ -24,30 +24,31 @@ * @licend */ - (function clear() { //Code isolation +(function clear() { + //Code isolation - function clearBoard() { - var msg = { - "type": "clear", - "id": "", - "token": Tools.token - }; - Tools.drawAndSend(msg, Tools.list["Clear"]); - } + function clearBoard() { + var msg = { + type: "clear", + id: "", + token: Tools.token, + }; + Tools.drawAndSend(msg, Tools.list["Clear"]); + } - function draw(data) { - Tools.drawingArea.innerHTML = ""; - } + function draw(data) { + 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", - }); - -})(); //End of code isolation \ No newline at end of file + 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 diff --git a/client-data/tools/cursor/cursor.js b/client-data/tools/cursor/cursor.js index a09b01db..43cffe47 100644 --- a/client-data/tools/cursor/cursor.js +++ b/client-data/tools/cursor/cursor.js @@ -24,90 +24,109 @@ * @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; + // 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 CURSOR_DELETE_AFTER_MS = 1000 * 5; + var CURSOR_DELETE_AFTER_MS = 1000 * 5; - var lastCursorUpdate = 0; - var sending = true; + var lastCursorUpdate = 0; + var sending = true; - var cursorTool = { - "name": "Cursor", - "listeners": { - "press": function () { sending = false }, - "move": handleMarker, - "release": function () { sending = true }, - }, - "onSizeChange": onSizeChange, - "draw": draw, - "mouseCursor": "crosshair", - "icon": "tools/pencil/icon.svg", - }; - Tools.register(cursorTool); - Tools.addToolListeners(cursorTool); + var cursorTool = { + name: "Cursor", + listeners: { + press: function () { + sending = false; + }, + move: handleMarker, + release: function () { + sending = true; + }, + }, + onSizeChange: onSizeChange, + draw: draw, + mouseCursor: "crosshair", + icon: "tools/pencil/icon.svg", + }; + Tools.register(cursorTool); + Tools.addToolListeners(cursorTool); - var message = { - type: "update", - x: 0, - y: 0, - color: Tools.getColor(), - size: Tools.getSize(), - }; + var message = { + type: "update", + x: 0, + y: 0, + color: Tools.getColor(), + size: Tools.getSize(), + }; - function handleMarker(x, y) { - // throttle local cursor updates - message.x = x; - message.y = y; - message.color = Tools.getColor(); - message.size = Tools.getSize(); - updateMarker(); - } + function handleMarker(x, y) { + // throttle local cursor updates + message.x = x; + message.y = y; + message.color = Tools.getColor(); + message.size = Tools.getSize(); + updateMarker(); + } - function onSizeChange(size) { - message.size = size; - updateMarker(); - } + function onSizeChange(size) { + message.size = size; + updateMarker(); + } - 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)) { - Tools.drawAndSend(message, cursorTool); - lastCursorUpdate = cur_time; - } else { - draw(message); - } + 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) + ) { + Tools.drawAndSend(message, cursorTool); + lastCursorUpdate = cur_time; + } else { + draw(message); } + } - var cursorsElem = Tools.svg.getElementById("cursors"); + var cursorsElem = Tools.svg.getElementById("cursors"); - function createCursor(id) { - 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); - cursor.setAttributeNS(null, "cy", 0); - cursor.setAttributeNS(null, "r", 10); - cursorsElem.appendChild(cursor); - setTimeout(function () { - cursorsElem.removeChild(cursor); - }, CURSOR_DELETE_AFTER_MS); - return cursor; - } + function createCursor(id) { + 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); + cursor.setAttributeNS(null, "cy", 0); + cursor.setAttributeNS(null, "r", 10); + cursorsElem.appendChild(cursor); + setTimeout(function () { + cursorsElem.removeChild(cursor); + }, CURSOR_DELETE_AFTER_MS); + return cursor; + } - function getCursor(id) { - return document.getElementById(id) || createCursor(id); - } + function getCursor(id) { + return document.getElementById(id) || createCursor(id); + } - 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 + ")"); - cursor.setAttributeNS(null, "fill", message.color); - cursor.setAttributeNS(null, "r", message.size / 2); - } -})(); \ No newline at end of file + 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 + ")", + ); + cursor.setAttributeNS(null, "fill", message.color); + cursor.setAttributeNS(null, "r", message.size / 2); + } +})(); diff --git a/client-data/tools/download/download.js b/client-data/tools/download/download.js index 8fe67256..211f53a3 100644 --- a/client-data/tools/download/download.js +++ b/client-data/tools/download/download.js @@ -24,59 +24,67 @@ * @licend */ -(function download() { //Code isolation +(function download() { + //Code isolation - function downloadSVGFile() { - var canvasCopy = Tools.svg.cloneNode(true); - canvasCopy.removeAttribute("style", ""); // Remove css transform - var styleNode = document.createElement("style"); + function downloadSVGFile() { + var canvasCopy = Tools.svg.cloneNode(true); + canvasCopy.removeAttribute("style", ""); // Remove css transform + var styleNode = document.createElement("style"); - // 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/))) { - // This is a Stylesheet from a Tool or the Board itself, so we should include it - return true; - } - // Not a stylesheet of the tool, so we can ignore it for export - return false; - }) - .map(function (stylesheet) { - return Array.from(stylesheet.cssRules) - .map(function (rule) { return rule.cssText }) - }).join("\n") + // 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/)) + ) { + // This is a Stylesheet from a Tool or the Board itself, so we should include it + return true; + } + // Not a stylesheet of the tool, so we can ignore it for export + return false; + }) + .map(function (stylesheet) { + 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' }); - downloadContent(blob, Tools.boardName + ".svg"); - } + canvasCopy.appendChild(styleNode); + 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 - 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'; - document.body.appendChild(element); - element.click(); - document.body.removeChild(element); - window.URL.revokeObjectURL(url); - } + function downloadContent(blob, filename) { + 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"; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + window.URL.revokeObjectURL(url); } + } - 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 \ No newline at end of file + 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 diff --git a/client-data/tools/ellipse/ellipse.css b/client-data/tools/ellipse/ellipse.css index 599ff07d..fb45fa71 100644 --- a/client-data/tools/ellipse/ellipse.css +++ b/client-data/tools/ellipse/ellipse.css @@ -1,3 +1,3 @@ #canvas ellipse { - fill: none; + fill: none; } diff --git a/client-data/tools/ellipse/ellipse.js b/client-data/tools/ellipse/ellipse.js index 2b0a7621..1450fac1 100644 --- a/client-data/tools/ellipse/ellipse.js +++ b/client-data/tools/ellipse/ellipse.js @@ -24,149 +24,160 @@ * @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 - }); - - curUpdate.id = curUpdate.id; - curUpdate.x = x; - curUpdate.y = y; - } - - function move(x, y, evt) { - if (!curUpdate.id) return; // Not currently drawing - if (evt) { - circleTool.secondary.active = circleTool.secondary.active || evt.shiftKey; - evt.preventDefault(); - } - lastPos.x = x; - lastPos.y = y; - doUpdate(); + 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, + }); + + curUpdate.id = curUpdate.id; + curUpdate.x = x; + curUpdate.y = y; + } + + function move(x, y, evt) { + if (!curUpdate.id) return; // Not currently drawing + if (evt) { + circleTool.secondary.active = circleTool.secondary.active || evt.shiftKey; + evt.preventDefault(); } - - 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 diameter = Math.max(Math.abs(deltaX), Math.abs(deltaY)); - curUpdate['x2'] = x0 + (deltaX > 0 ? diameter : -diameter); - curUpdate['y2'] = y0 + (deltaY > 0 ? diameter : -diameter); - } else { - curUpdate['x2'] = lastPos.x; - curUpdate['y2'] = lastPos.y; - } - - if (performance.now() - lastTime > 70 || force) { - Tools.drawAndSend(curUpdate); - lastTime = performance.now(); - } else { - draw(curUpdate); - } + lastPos.x = x; + lastPos.y = y; + doUpdate(); + } + + 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 diameter = Math.max(Math.abs(deltaX), Math.abs(deltaY)); + curUpdate["x2"] = x0 + (deltaX > 0 ? diameter : -diameter); + curUpdate["y2"] = y0 + (deltaY > 0 ? diameter : -diameter); + } else { + curUpdate["x2"] = lastPos.x; + curUpdate["y2"] = lastPos.y; } - function stop(x, y) { - lastPos.x = x; - lastPos.y = y; - doUpdate(true); - curUpdate.id = ""; + if (performance.now() - lastTime > 70 || force) { + Tools.drawAndSend(curUpdate); + lastTime = performance.now(); + } else { + draw(curUpdate); } - - function draw(data) { - Tools.drawingEvent = true; - switch (data.type) { - case "ellipse": - createShape(data); - break; - case "update": - 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'] - }); - } - updateShape(shape, data); - break; - default: - console.error("Ellipse: Draw instruction with unknown type. ", data); - break; + } + + function stop(x, y) { + lastPos.x = x; + lastPos.y = y; + doUpdate(true); + curUpdate.id = ""; + } + + function draw(data) { + Tools.drawingEvent = true; + switch (data.type) { + case "ellipse": + createShape(data); + break; + case "update": + 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"], + }); } - } - - 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"); 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); - Tools.drawingArea.appendChild(shape); - return shape; + break; + default: + console.error("Ellipse: Draw instruction with unknown type. ", data); + break; } - - 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; - } - - 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, - }, - "shortcut": "c", - "listeners": { - "press": start, - "move": move, - "release": stop, - }, - "draw": draw, - "mouseCursor": "crosshair", - "stylesheet": "tools/ellipse/ellipse.css" - }; - Tools.add(circleTool); - + } + + 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"); + 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, + ); + 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; + } + + 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, + }, + shortcut: "c", + listeners: { + press: start, + move: move, + release: stop, + }, + draw: draw, + mouseCursor: "crosshair", + stylesheet: "tools/ellipse/ellipse.css", + }; + Tools.add(circleTool); })(); //End of code isolation diff --git a/client-data/tools/eraser/eraser.js b/client-data/tools/eraser/eraser.js index e8ec74b6..1a856601 100644 --- a/client-data/tools/eraser/eraser.js +++ b/client-data/tools/eraser/eraser.js @@ -1,7 +1,7 @@ /** * WHITEBOPHIR ********************************************************* - * @licstart The following is the entire license notice for the + * @licstart The following is the entire license notice for the * JavaScript code in this page. * * Copyright (C) 2013 Ophir LOJKINE @@ -24,74 +24,83 @@ * @licend */ -(function eraser() { //Code isolation +(function eraser() { + //Code isolation - var erasing = false; + var erasing = false; - function startErasing(x, y, evt) { - //Prevent the press from being interpreted by the browser - evt.preventDefault(); - erasing = true; - erase(x, y, evt); - } + function startErasing(x, y, evt) { + //Prevent the press from being interpreted by the browser + evt.preventDefault(); + erasing = true; + erase(x, y, evt); + } - var msg = { - "type": "delete", - "id": "" - }; + var msg = { + type: "delete", + id: "", + }; - function inDrawingArea(elem) { - return Tools.drawingArea.contains(elem); - } + function inDrawingArea(elem) { + return Tools.drawingArea.contains(elem); + } - function erase(x, y, evt) { - // evt.target should be the element over which the mouse is... - var target = evt.target; - if (evt.type === "touchmove") { - // ... the target of touchmove events is the element that was initially touched, - // not the one **currently** being touched - var touch = evt.touches[0]; - target = document.elementFromPoint(touch.clientX, touch.clientY); - } - if (erasing && target !== Tools.svg && target !== Tools.drawingArea && inDrawingArea(target)) { - msg.id = target.id; - Tools.drawAndSend(msg); - } - } + function erase(x, y, evt) { + // evt.target should be the element over which the mouse is... + var target = evt.target; + if (evt.type === "touchmove") { + // ... the target of touchmove events is the element that was initially touched, + // not the one **currently** being touched + var touch = evt.touches[0]; + target = document.elementFromPoint(touch.clientX, touch.clientY); + } + if ( + erasing && + target !== Tools.svg && + target !== Tools.drawingArea && + inDrawingArea(target) + ) { + msg.id = target.id; + Tools.drawAndSend(msg); + } + } - function stopErasing() { - erasing = false; - } + function stopErasing() { + erasing = false; + } - function draw(data) { - var elem; - switch (data.type) { - //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."); - else Tools.drawingArea.removeChild(elem); - break; - default: - console.error("Eraser: 'delete' instruction with unknown type. ", data); - break; - } - } + function draw(data) { + var elem; + switch (data.type) { + //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.", + ); + else Tools.drawingArea.removeChild(elem); + break; + default: + console.error("Eraser: 'delete' instruction with unknown type. ", data); + break; + } + } - var svg = Tools.svg; - - 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, - }); + var svg = Tools.svg; + 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, + }); })(); //End of code isolation diff --git a/client-data/tools/grid/grid.js b/client-data/tools/grid/grid.js index feca5047..be6885d3 100644 --- a/client-data/tools/grid/grid.js +++ b/client-data/tools/grid/grid.js @@ -24,95 +24,102 @@ * @licend */ -(function grid() { //Code isolation +(function grid() { + //Code isolation - var index = 0; //grid off by default - var states = ["none", "url(#grid)", "url(#dots)"]; + var index = 0; //grid off by default + var states = ["none", "url(#grid)", "url(#dots)"]; - function toggleGrid(evt) { - index = (index + 1) % states.length; - gridContainer.setAttributeNS(null, "fill", states[index]); - } + function toggleGrid(evt) { + index = (index + 1) % states.length; + gridContainer.setAttributeNS(null, "fill", states[index]); + } - function createPatterns() { - // create patterns - // small (inner) grid - var smallGrid = Tools.createSVGElement("pattern", { - id: "smallGrid", - width: "30", - height: "30", - patternUnits: "userSpaceOnUse" - }); - smallGrid.appendChild( - Tools.createSVGElement("path", { - d: "M 30 0 L 0 0 0 30", - fill: "none", - stroke: "gray", - 'stroke-width': "0.5" - }) - ); - // (outer) grid - var grid = Tools.createSVGElement("pattern", { - id: "grid", - width: "300", - height: "300", - patternUnits: "userSpaceOnUse" - }); - grid.appendChild(Tools.createSVGElement("rect", { - width: "300", - height: "300", - fill: "url(#smallGrid)" - })); - grid.appendChild( - Tools.createSVGElement("path", { - d: "M 300 0 L 0 0 0 300", - fill: "none", - stroke: "gray", 'stroke-width': "1" - }) - ); - // dots - var dots = Tools.createSVGElement("pattern", { - id: "dots", - width: "30", - height: "30", - x: "-10", - y: "-10", - patternUnits: "userSpaceOnUse" - }); - dots.appendChild(Tools.createSVGElement("circle", { - fill: "gray", - cx: "10", - cy: "10", - r: "2" - })); - - var defs = Tools.svg.getElementById("defs"); - defs.appendChild(smallGrid); - defs.appendChild(grid); - defs.appendChild(dots); - } + function createPatterns() { + // create patterns + // small (inner) grid + var smallGrid = Tools.createSVGElement("pattern", { + id: "smallGrid", + width: "30", + height: "30", + patternUnits: "userSpaceOnUse", + }); + smallGrid.appendChild( + Tools.createSVGElement("path", { + d: "M 30 0 L 0 0 0 30", + fill: "none", + stroke: "gray", + "stroke-width": "0.5", + }), + ); + // (outer) grid + var grid = Tools.createSVGElement("pattern", { + id: "grid", + width: "300", + height: "300", + patternUnits: "userSpaceOnUse", + }); + grid.appendChild( + Tools.createSVGElement("rect", { + width: "300", + height: "300", + fill: "url(#smallGrid)", + }), + ); + grid.appendChild( + Tools.createSVGElement("path", { + d: "M 300 0 L 0 0 0 300", + fill: "none", + stroke: "gray", + "stroke-width": "1", + }), + ); + // dots + var dots = Tools.createSVGElement("pattern", { + id: "dots", + width: "30", + height: "30", + x: "-10", + y: "-10", + patternUnits: "userSpaceOnUse", + }); + dots.appendChild( + Tools.createSVGElement("circle", { + fill: "gray", + cx: "10", + cy: "10", + r: "2", + }), + ); - var gridContainer = (function init() { - // initialize patterns - createPatterns(); - // create grid container - var gridContainer = Tools.createSVGElement("rect", { - id: "gridContainer", - width: "100%", height: "100%", - fill: states[index] - }); - Tools.svg.insertBefore(gridContainer, Tools.drawingArea); - return gridContainer; - })(); + var defs = Tools.svg.getElementById("defs"); + defs.appendChild(smallGrid); + defs.appendChild(grid); + defs.appendChild(dots); + } - Tools.add({ //The new tool - "name": "Grid", - "shortcut": "g", - "listeners": {}, - "icon": "tools/grid/icon.svg", - "oneTouch": true, - "onstart": toggleGrid, - "mouseCursor": "crosshair", + var gridContainer = (function init() { + // initialize patterns + createPatterns(); + // create grid container + var gridContainer = Tools.createSVGElement("rect", { + id: "gridContainer", + width: "100%", + height: "100%", + fill: states[index], }); + Tools.svg.insertBefore(gridContainer, Tools.drawingArea); + return gridContainer; + })(); -})(); //End of code isolation \ No newline at end of file + 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 diff --git a/client-data/tools/hand/hand.js b/client-data/tools/hand/hand.js index 86980a6d..51133350 100644 --- a/client-data/tools/hand/hand.js +++ b/client-data/tools/hand/hand.js @@ -1,7 +1,7 @@ /** * WHITEBOPHIR ********************************************************* - * @licstart The following is the entire license notice for the + * @licstart The following is the entire license notice for the * JavaScript code in this page. * * Copyright (C) 2013 Ophir LOJKINE @@ -24,502 +24,556 @@ * @licend */ -(function hand() { //Code isolation - var selectorStates = { - pointing: 0, - selecting: 1, - transform: 2 - } - var selected = null; - var selected_els = []; - var selectionRect = createSelectorRect(); - var selectionRectTransform; - var currentTransform = null; - var transform_elements = []; - var selectorState = selectorStates.pointing; - var last_sent = 0; - var blockedSelectionButtons = Tools.server_config.BLOCKED_SELECTION_BUTTONS; - var selectionButtons = [ - createButton("delete", "delete", 24, 24, - function (me, bbox, s) { - me.width.baseVal.value = me.origWidth / s; - me.height.baseVal.value = me.origHeight / s; - me.x.baseVal.value = bbox.r[0]; - me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; - me.style.display = ""; - }, - deleteSelection), - - createButton("duplicate", "duplicate", 24, 24, - function (me, bbox, s) { - me.width.baseVal.value = me.origWidth / s; - me.height.baseVal.value = me.origHeight / s; - me.x.baseVal.value = bbox.r[0] + (me.origWidth + 2) / s; - me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; - me.style.display = ""; - }, - duplicateSelection), - - createButton("scaleHandle", "handle", 14, 14, - function (me, bbox, s) { - me.width.baseVal.value = me.origWidth / s; - me.height.baseVal.value = me.origHeight / s; - me.x.baseVal.value = bbox.r[0] + bbox.a[0] - me.origWidth / (2 * s); - me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight / (2 * s); - me.style.display = ""; - }, - startScalingTransform) - ]; - - for (i in blockedSelectionButtons) { - delete selectionButtons[blockedSelectionButtons[i]]; - } - - var getScale = Tools.getScale; - - function getParentMathematics(el) { - var target; - var a = el; - var els = []; - while (a) { - els.unshift(a); - a = a.parentElement; - } - var parentMathematics = els.find(function (el) { - return el.getAttribute("class") === "MathElement"; - }); - if ((parentMathematics) && parentMathematics.tagName === "svg") { - target = parentMathematics; - } - return target || el; - } - - function deleteSelection() { - var msgs = selected_els.map(function (el) { - return ({ - "type": "delete", - "id": el.id - }); - }); - var data = { - _children: msgs - } - Tools.drawAndSend(data); - selected_els = []; - hideSelectionUI(); - } - - function duplicateSelection() { - if (!(selectorState == selectorStates.pointing) - || (selected_els.length == 0)) return; - var msgs = []; - var newids = []; - for (var i = 0; i < selected_els.length; i++) { - var id = selected_els[i].id; - msgs[i] = { - type: "copy", - id: id, - newid: Tools.generateUID(id[0]) - }; - newids[i] = id; - } - Tools.drawAndSend({ _children: msgs }); - selected_els = newids.map(function (id) { - return Tools.svg.getElementById(id); - }); - } - - function createSelectorRect() { - var shape = Tools.createSVGElement("rect"); - shape.id = "selectionRect"; - shape.x.baseVal.value = 0; - shape.y.baseVal.value = 0; - shape.width.baseVal.value = 0; - shape.height.baseVal.value = 0; - shape.setAttribute("stroke", "black"); - shape.setAttribute("stroke-width", 1); - shape.setAttribute("vector-effect", "non-scaling-stroke"); - shape.setAttribute("fill", "none"); - shape.setAttribute("stroke-dasharray", "5 5"); - shape.setAttribute("opacity", 1); - Tools.svg.appendChild(shape); - return shape; - } - - function createButton(name, icon, width, height, drawCallback, clickCallback) { - var shape = Tools.createSVGElement("image", { - href: "tools/hand/" + icon + ".svg", - width: width, height: height - }); - shape.style.display = "none"; - shape.origWidth = width; - shape.origHeight = height; - shape.drawCallback = drawCallback; - shape.clickCallback = clickCallback; - Tools.svg.appendChild(shape); - return shape; - } - - function showSelectionButtons() { - var scale = getScale(); - var selectionBBox = selectionRect.transformedBBox(); - for (var i = 0; i < selectionButtons.length; i++) { - selectionButtons[i].drawCallback(selectionButtons[i], - selectionBBox, - scale); - } - } - - function hideSelectionButtons() { - for (var i = 0; i < selectionButtons.length; i++) { - selectionButtons[i].style.display = "none"; - } - } - - function hideSelectionUI() { - hideSelectionButtons(); - selectionRect.style.display = "none"; - } - - function startMovingElements(x, y, evt) { - evt.preventDefault(); - selectorState = selectorStates.transform; - currentTransform = moveSelection; - selected = { x: x, y: y }; - // Some of the selected elements could have been deleted - selected_els = selected_els.filter(function (el) { - return Tools.svg.getElementById(el.id) !== null; - }); - 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 - }; - }); - var tmatrix = get_transform_matrix(selectionRect); - selectionRectTransform = { x: tmatrix.e, y: tmatrix.f }; - } - - function startScalingTransform(x, y, evt) { - evt.preventDefault(); - hideSelectionButtons(); - selectorState = selectorStates.transform; - var bbox = selectionRect.transformedBBox(); - selected = { - x: bbox.r[0], - y: bbox.r[1], - w: bbox.a[0], - h: bbox.b[1], - }; - 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 - }; - }); - var tmatrix = get_transform_matrix(selectionRect); - selectionRectTransform = { - a: tmatrix.a, d: tmatrix.d, - e: tmatrix.e, f: tmatrix.f - }; - currentTransform = scaleSelection; - } - - function startSelector(x, y, evt) { - evt.preventDefault(); - selected = { x: x, y: y }; - selected_els = []; - selectorState = selectorStates.selecting; - selectionRect.x.baseVal.value = x; - selectionRect.y.baseVal.value = y; - selectionRect.width.baseVal.value = 0; - selectionRect.height.baseVal.value = 0; - selectionRect.style.display = ""; - tmatrix = get_transform_matrix(selectionRect); - tmatrix.e = 0; - 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())) - selected.push(Tools.drawingArea.children[i]); - } - return selected; - } - - function moveSelection(x, y) { - var dx = x - selected.x; - var dy = y - selected.y; - var msgs = selected_els.map(function (el, i) { - var oldTransform = transform_elements[i]; - return { - type: "update", - id: el.id, - transform: { - a: oldTransform.a, - b: oldTransform.b, - c: oldTransform.c, - d: oldTransform.d, - e: dx + oldTransform.e, - f: dy + oldTransform.f - } - }; - }) - var msg = { - _children: msgs - }; - var tmatrix = get_transform_matrix(selectionRect); - tmatrix.e = dx + selectionRectTransform.x; - tmatrix.f = dy + selectionRectTransform.y; - var now = performance.now(); - if (now - last_sent > 70) { - last_sent = now; - Tools.drawAndSend(msg); - } else { - draw(msg); - } - } - - function scaleSelection(x, y) { - 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 - return { - type: "update", - id: el.id, - transform: { - a: a, - b: oldTransform.b, - c: oldTransform.c, - d: d, - e: e, - f: f - } - }; - }) - var msg = { - _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) - var now = performance.now(); - if (now - last_sent > 70) { - last_sent = now; - Tools.drawAndSend(msg); - } else { - draw(msg); - } - } - - function updateRect(x, y, rect) { - rect.x.baseVal.value = Math.min(x, selected.x); - rect.y.baseVal.value = Math.min(y, selected.y); - rect.width.baseVal.value = Math.abs(x - selected.x); - rect.height.baseVal.value = Math.abs(y - selected.y); - } - - function resetSelectionRect() { - var bbox = selectionRect.transformedBBox(); - var tmatrix = get_transform_matrix(selectionRect); - selectionRect.x.baseVal.value = bbox.r[0]; - 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; - } - - function get_transform_matrix(elem) { - // Returns the first translate or transform matrix or makes one - var transform = null; - for (var i = 0; i < elem.transform.baseVal.numberOfItems; ++i) { - var baseVal = elem.transform.baseVal[i]; - // quick tests showed that even if one changes only the fields e and f or uses createSVGTransformFromMatrix - // the brower may add a SVG_TRANSFORM_MATRIX instead of a SVG_TRANSFORM_TRANSLATE - if (baseVal.type === SVGTransform.SVG_TRANSFORM_MATRIX) { - transform = baseVal; - break; - } - } - if (transform == null) { - transform = elem.transform.baseVal.createSVGTransformFromMatrix(Tools.svg.createSVGMatrix()); - elem.transform.baseVal.appendItem(transform); - } - return transform.matrix; - } - - function draw(data) { - if (data._children) { - batchCall(draw, data._children); - } - 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."); - var tmatrix = get_transform_matrix(elem); - for (i in data.transform) { - tmatrix[i] = data.transform[i] - } - break; - case "copy": - var newElement = Tools.svg.getElementById(data.id).cloneNode(true); - newElement.id = data.newid; - Tools.drawingArea.appendChild(newElement); - break; - case "delete": - data.tool = "Eraser"; - messageForTool(data); - break; - default: - throw new Error("Mover: 'move' instruction with unknown type. ", data); - } - } - } - - function clickSelector(x, y, evt) { - selectionRect = selectionRect || createSelectorRect(); - for (var i = 0; i < selectionButtons.length; i++) { - if (selectionButtons[i].contains(evt.target)) { - var button = selectionButtons[i]; - } - } - if (button) { - button.clickCallback(x, y, evt); - } else if (pointInTransformedBBox([x, y], selectionRect.transformedBBox())) { - hideSelectionButtons(); - startMovingElements(x, y, evt); - } else if (Tools.drawingArea.contains(evt.target)) { - hideSelectionUI(); - selected_els = [getParentMathematics(evt.target)]; - startMovingElements(x, y, evt); - } else { - hideSelectionButtons(); - startSelector(x, y, evt); - } - } - - function releaseSelector(x, y, evt) { - if (selectorState == selectorStates.selecting) { - selected_els = calculateSelection(); - if (selected_els.length == 0) { - hideSelectionUI(); - } - } else if (selectorState == selectorStates.transform) - resetSelectionRect(); - if (selected_els.length != 0) showSelectionButtons(); - transform_elements = []; - selectorState = selectorStates.pointing; - } - - function moveSelector(x, y, evt) { - if (selectorState == selectorStates.selecting) { - updateRect(x, y, selectionRect); - } else if (selectorState == selectorStates.transform && currentTransform) { - currentTransform(x, y); - } - } - - function startHand(x, y, evt, isTouchEvent) { - if (!isTouchEvent) { - 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 - window.scrollTo(selected.x - evt.clientX, selected.y - evt.clientY); - } - } - - function press(x, y, evt, isTouchEvent) { - if (!handTool.secondary.active) startHand(x, y, evt, isTouchEvent); - 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); - } - - function release(x, y, evt, isTouchEvent) { - move(x, y, evt, isTouchEvent); - if (handTool.secondary.active) releaseSelector(x, y, evt, isTouchEvent); - selected = null; - } - - function deleteShortcut(e) { - 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")) - duplicateSelection(); - } - - function switchTool() { - onquit(); - if (handTool.secondary.active) { - window.addEventListener("keydown", deleteShortcut); - window.addEventListener("keydown", duplicateShortcut); - } - } - - function onquit() { - selected = null; - hideSelectionUI(); - window.removeEventListener("keydown", deleteShortcut); - window.removeEventListener("keydown", duplicateShortcut); - } - - 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, - }, - "draw": draw, - "icon": "tools/hand/hand.svg", - "mouseCursor": "move", - "showMarker": true, - }; - Tools.add(handTool); - Tools.change("Hand"); // Use the hand tool by default +(function hand() { + //Code isolation + var selectorStates = { + pointing: 0, + selecting: 1, + transform: 2, + }; + var selected = null; + var selected_els = []; + var selectionRect = createSelectorRect(); + var selectionRectTransform; + var currentTransform = null; + var transform_elements = []; + var selectorState = selectorStates.pointing; + var last_sent = 0; + var blockedSelectionButtons = Tools.server_config.BLOCKED_SELECTION_BUTTONS; + var selectionButtons = [ + createButton( + "delete", + "delete", + 24, + 24, + function (me, bbox, s) { + me.width.baseVal.value = me.origWidth / s; + me.height.baseVal.value = me.origHeight / s; + me.x.baseVal.value = bbox.r[0]; + me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; + me.style.display = ""; + }, + deleteSelection, + ), + + createButton( + "duplicate", + "duplicate", + 24, + 24, + function (me, bbox, s) { + me.width.baseVal.value = me.origWidth / s; + me.height.baseVal.value = me.origHeight / s; + me.x.baseVal.value = bbox.r[0] + (me.origWidth + 2) / s; + me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s; + me.style.display = ""; + }, + duplicateSelection, + ), + + createButton( + "scaleHandle", + "handle", + 14, + 14, + function (me, bbox, s) { + me.width.baseVal.value = me.origWidth / s; + me.height.baseVal.value = me.origHeight / s; + me.x.baseVal.value = bbox.r[0] + bbox.a[0] - me.origWidth / (2 * s); + me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight / (2 * s); + me.style.display = ""; + }, + startScalingTransform, + ), + ]; + + for (i in blockedSelectionButtons) { + delete selectionButtons[blockedSelectionButtons[i]]; + } + + var getScale = Tools.getScale; + + function getParentMathematics(el) { + var target; + var a = el; + var els = []; + while (a) { + els.unshift(a); + a = a.parentElement; + } + var parentMathematics = els.find(function (el) { + return el.getAttribute("class") === "MathElement"; + }); + if (parentMathematics && parentMathematics.tagName === "svg") { + target = parentMathematics; + } + return target || el; + } + + function deleteSelection() { + var msgs = selected_els.map(function (el) { + return { + type: "delete", + id: el.id, + }; + }); + var data = { + _children: msgs, + }; + Tools.drawAndSend(data); + selected_els = []; + hideSelectionUI(); + } + + function duplicateSelection() { + if (!(selectorState == selectorStates.pointing) || selected_els.length == 0) + return; + var msgs = []; + var newids = []; + for (var i = 0; i < selected_els.length; i++) { + var id = selected_els[i].id; + msgs[i] = { + type: "copy", + id: id, + newid: Tools.generateUID(id[0]), + }; + newids[i] = id; + } + Tools.drawAndSend({ _children: msgs }); + selected_els = newids.map(function (id) { + return Tools.svg.getElementById(id); + }); + } + + function createSelectorRect() { + var shape = Tools.createSVGElement("rect"); + shape.id = "selectionRect"; + shape.x.baseVal.value = 0; + shape.y.baseVal.value = 0; + shape.width.baseVal.value = 0; + shape.height.baseVal.value = 0; + shape.setAttribute("stroke", "black"); + shape.setAttribute("stroke-width", 1); + shape.setAttribute("vector-effect", "non-scaling-stroke"); + shape.setAttribute("fill", "none"); + shape.setAttribute("stroke-dasharray", "5 5"); + shape.setAttribute("opacity", 1); + Tools.svg.appendChild(shape); + return shape; + } + + function createButton( + name, + icon, + width, + height, + drawCallback, + clickCallback, + ) { + var shape = Tools.createSVGElement("image", { + href: "tools/hand/" + icon + ".svg", + width: width, + height: height, + }); + shape.style.display = "none"; + shape.origWidth = width; + shape.origHeight = height; + shape.drawCallback = drawCallback; + shape.clickCallback = clickCallback; + Tools.svg.appendChild(shape); + return shape; + } + + function showSelectionButtons() { + var scale = getScale(); + var selectionBBox = selectionRect.transformedBBox(); + for (var i = 0; i < selectionButtons.length; i++) { + selectionButtons[i].drawCallback( + selectionButtons[i], + selectionBBox, + scale, + ); + } + } + + function hideSelectionButtons() { + for (var i = 0; i < selectionButtons.length; i++) { + selectionButtons[i].style.display = "none"; + } + } + + function hideSelectionUI() { + hideSelectionButtons(); + selectionRect.style.display = "none"; + } + + function startMovingElements(x, y, evt) { + evt.preventDefault(); + selectorState = selectorStates.transform; + currentTransform = moveSelection; + selected = { x: x, y: y }; + // Some of the selected elements could have been deleted + selected_els = selected_els.filter(function (el) { + return Tools.svg.getElementById(el.id) !== null; + }); + 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, + }; + }); + var tmatrix = get_transform_matrix(selectionRect); + selectionRectTransform = { x: tmatrix.e, y: tmatrix.f }; + } + + function startScalingTransform(x, y, evt) { + evt.preventDefault(); + hideSelectionButtons(); + selectorState = selectorStates.transform; + var bbox = selectionRect.transformedBBox(); + selected = { + x: bbox.r[0], + y: bbox.r[1], + w: bbox.a[0], + h: bbox.b[1], + }; + 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, + }; + }); + var tmatrix = get_transform_matrix(selectionRect); + selectionRectTransform = { + a: tmatrix.a, + d: tmatrix.d, + e: tmatrix.e, + f: tmatrix.f, + }; + currentTransform = scaleSelection; + } + + function startSelector(x, y, evt) { + evt.preventDefault(); + selected = { x: x, y: y }; + selected_els = []; + selectorState = selectorStates.selecting; + selectionRect.x.baseVal.value = x; + selectionRect.y.baseVal.value = y; + selectionRect.width.baseVal.value = 0; + selectionRect.height.baseVal.value = 0; + selectionRect.style.display = ""; + tmatrix = get_transform_matrix(selectionRect); + tmatrix.e = 0; + 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()) + ) + selected.push(Tools.drawingArea.children[i]); + } + return selected; + } + + function moveSelection(x, y) { + var dx = x - selected.x; + var dy = y - selected.y; + var msgs = selected_els.map(function (el, i) { + var oldTransform = transform_elements[i]; + return { + type: "update", + id: el.id, + transform: { + a: oldTransform.a, + b: oldTransform.b, + c: oldTransform.c, + d: oldTransform.d, + e: dx + oldTransform.e, + f: dy + oldTransform.f, + }, + }; + }); + var msg = { + _children: msgs, + }; + var tmatrix = get_transform_matrix(selectionRect); + tmatrix.e = dx + selectionRectTransform.x; + tmatrix.f = dy + selectionRectTransform.y; + var now = performance.now(); + if (now - last_sent > 70) { + last_sent = now; + Tools.drawAndSend(msg); + } else { + draw(msg); + } + } + + function scaleSelection(x, y) { + 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; + return { + type: "update", + id: el.id, + transform: { + a: a, + b: oldTransform.b, + c: oldTransform.c, + d: d, + e: e, + f: f, + }, + }; + }); + var msg = { + _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); + var now = performance.now(); + if (now - last_sent > 70) { + last_sent = now; + Tools.drawAndSend(msg); + } else { + draw(msg); + } + } + + function updateRect(x, y, rect) { + rect.x.baseVal.value = Math.min(x, selected.x); + rect.y.baseVal.value = Math.min(y, selected.y); + rect.width.baseVal.value = Math.abs(x - selected.x); + rect.height.baseVal.value = Math.abs(y - selected.y); + } + + function resetSelectionRect() { + var bbox = selectionRect.transformedBBox(); + var tmatrix = get_transform_matrix(selectionRect); + selectionRect.x.baseVal.value = bbox.r[0]; + 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; + } + + function get_transform_matrix(elem) { + // Returns the first translate or transform matrix or makes one + var transform = null; + for (var i = 0; i < elem.transform.baseVal.numberOfItems; ++i) { + var baseVal = elem.transform.baseVal[i]; + // quick tests showed that even if one changes only the fields e and f or uses createSVGTransformFromMatrix + // the brower may add a SVG_TRANSFORM_MATRIX instead of a SVG_TRANSFORM_TRANSLATE + if (baseVal.type === SVGTransform.SVG_TRANSFORM_MATRIX) { + transform = baseVal; + break; + } + } + if (transform == null) { + transform = elem.transform.baseVal.createSVGTransformFromMatrix( + Tools.svg.createSVGMatrix(), + ); + elem.transform.baseVal.appendItem(transform); + } + return transform.matrix; + } + + function draw(data) { + if (data._children) { + batchCall(draw, data._children); + } 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.", + ); + var tmatrix = get_transform_matrix(elem); + for (i in data.transform) { + tmatrix[i] = data.transform[i]; + } + break; + case "copy": + var newElement = Tools.svg.getElementById(data.id).cloneNode(true); + newElement.id = data.newid; + Tools.drawingArea.appendChild(newElement); + break; + case "delete": + data.tool = "Eraser"; + messageForTool(data); + break; + default: + throw new Error( + "Mover: 'move' instruction with unknown type. ", + data, + ); + } + } + } + + function clickSelector(x, y, evt) { + selectionRect = selectionRect || createSelectorRect(); + for (var i = 0; i < selectionButtons.length; i++) { + if (selectionButtons[i].contains(evt.target)) { + var button = selectionButtons[i]; + } + } + if (button) { + button.clickCallback(x, y, evt); + } else if ( + pointInTransformedBBox([x, y], selectionRect.transformedBBox()) + ) { + hideSelectionButtons(); + startMovingElements(x, y, evt); + } else if (Tools.drawingArea.contains(evt.target)) { + hideSelectionUI(); + selected_els = [getParentMathematics(evt.target)]; + startMovingElements(x, y, evt); + } else { + hideSelectionButtons(); + startSelector(x, y, evt); + } + } + + function releaseSelector(x, y, evt) { + if (selectorState == selectorStates.selecting) { + selected_els = calculateSelection(); + if (selected_els.length == 0) { + hideSelectionUI(); + } + } else if (selectorState == selectorStates.transform) resetSelectionRect(); + if (selected_els.length != 0) showSelectionButtons(); + transform_elements = []; + selectorState = selectorStates.pointing; + } + + function moveSelector(x, y, evt) { + if (selectorState == selectorStates.selecting) { + updateRect(x, y, selectionRect); + } else if (selectorState == selectorStates.transform && currentTransform) { + currentTransform(x, y); + } + } + + function startHand(x, y, evt, isTouchEvent) { + if (!isTouchEvent) { + 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 + window.scrollTo(selected.x - evt.clientX, selected.y - evt.clientY); + } + } + + function press(x, y, evt, isTouchEvent) { + if (!handTool.secondary.active) startHand(x, y, evt, isTouchEvent); + 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); + } + + function release(x, y, evt, isTouchEvent) { + move(x, y, evt, isTouchEvent); + if (handTool.secondary.active) releaseSelector(x, y, evt, isTouchEvent); + selected = null; + } + + function deleteShortcut(e) { + 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")) + duplicateSelection(); + } + + function switchTool() { + onquit(); + if (handTool.secondary.active) { + window.addEventListener("keydown", deleteShortcut); + window.addEventListener("keydown", duplicateShortcut); + } + } + + function onquit() { + selected = null; + hideSelectionUI(); + window.removeEventListener("keydown", deleteShortcut); + window.removeEventListener("keydown", duplicateShortcut); + } + + 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, + }, + draw: draw, + icon: "tools/hand/hand.svg", + mouseCursor: "move", + showMarker: true, + }; + Tools.add(handTool); + Tools.change("Hand"); // Use the hand tool by default })(); //End of code isolation diff --git a/client-data/tools/line/line.css b/client-data/tools/line/line.css index 3ccde940..a4a812ff 100755 --- a/client-data/tools/line/line.css +++ b/client-data/tools/line/line.css @@ -1,5 +1,5 @@ line { - fill: none; - stroke-linecap: round; - stroke-linejoin: round; + fill: none; + stroke-linecap: round; + stroke-linejoin: round; } diff --git a/client-data/tools/line/line.js b/client-data/tools/line/line.js index a51edb90..a3240f40 100644 --- a/client-data/tools/line/line.js +++ b/client-data/tools/line/line.js @@ -1,7 +1,7 @@ /** * WHITEBOPHIR ********************************************************* - * @licstart The following is the entire license notice for the + * @licstart The following is the entire license notice for the * JavaScript code in this page. * * Copyright (C) 2013 Ophir LOJKINE @@ -24,127 +24,138 @@ * @licend */ -(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 +(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.id = curLine.id; - this.x2 = x; - this.y2 = y; - } + //The data of the message that will be sent for every update + function UpdateMessage(x, y) { + this.type = "update"; + this.id = curLine.id; + this.x2 = x; + this.y2 = y; + } - function startLine(x, y, evt) { + function startLine(x, y, evt) { + //Prevent the press from being interpreted by the browser + evt.preventDefault(); - //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, + }; - curLine = { - '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); + } - Tools.drawAndSend(curLine); - } - - function continueLine(x, y, evt) { - /*Wait 70ms before adding any point to the currently drawing line. + function continueLine(x, y, evt) { + /*Wait 70ms before adding any point to the currently drawing line. This allows the animation to be smother*/ - if (curLine !== null) { - 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; - alpha = Math.round(alpha / increment) * increment; - x = curLine.x + d * Math.cos(alpha); - y = curLine.y + d * Math.sin(alpha); - } - if (performance.now() - lastTime > 70) { - Tools.drawAndSend(new UpdateMessage(x, y)); - lastTime = performance.now(); - } else { - draw(new UpdateMessage(x, y)); - } - } - if (evt) evt.preventDefault(); - } + if (curLine !== null) { + 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; + alpha = Math.round(alpha / increment) * increment; + x = curLine.x + d * Math.cos(alpha); + y = curLine.y + d * Math.sin(alpha); + } + if (performance.now() - lastTime > 70) { + Tools.drawAndSend(new UpdateMessage(x, y)); + lastTime = performance.now(); + } else { + draw(new UpdateMessage(x, y)); + } + } + if (evt) evt.preventDefault(); + } - function stopLine(x, y) { - //Add a last point to the line - continueLine(x, y); - curLine = null; - } + function stopLine(x, y) { + //Add a last point to the line + continueLine(x, y); + curLine = null; + } - function draw(data) { - switch (data.type) { - case "straight": - createLine(data); - break; - case "update": - 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'] - }); - } - updateLine(line, data); - break; - default: - console.error("Straight Line: Draw instruction with unknown type. ", data); - break; - } - } + function draw(data) { + switch (data.type) { + case "straight": + createLine(data); + break; + case "update": + 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"], + }); + } + updateLine(line, data); + break; + default: + console.error( + "Straight Line: Draw instruction with unknown type. ", + data, + ); + break; + } + } - 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"); - 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']; - //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); - Tools.drawingArea.appendChild(line); - return line; - } + 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"); + 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"]; + //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, + ); + Tools.drawingArea.appendChild(line); + return line; + } - function updateLine(line, data) { - line.x2.baseVal.value = data['x2']; - line.y2.baseVal.value = data['y2']; - } + function updateLine(line, data) { + 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, - }, - "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" - }; - Tools.add(lineTool); + var lineTool = { + name: "Straight line", + shortcut: "l", + listeners: { + press: startLine, + move: continueLine, + release: stopLine, + }, + 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", + }; + Tools.add(lineTool); })(); //End of code isolation diff --git a/client-data/tools/pencil/pencil.css b/client-data/tools/pencil/pencil.css index 66d3f9ef..65046cb5 100644 --- a/client-data/tools/pencil/pencil.css +++ b/client-data/tools/pencil/pencil.css @@ -1,5 +1,5 @@ path { - fill: none; - stroke-linecap: round; - stroke-linejoin: round; + fill: none; + stroke-linecap: round; + stroke-linejoin: round; } diff --git a/client-data/tools/pencil/pencil.js b/client-data/tools/pencil/pencil.js index 91476f8e..36b37d34 100644 --- a/client-data/tools/pencil/pencil.js +++ b/client-data/tools/pencil/pencil.js @@ -1,7 +1,7 @@ /** * WHITEBOPHIR ********************************************************* - * @licstart The following is the entire license notice for the + * @licstart The following is the entire license notice for the * JavaScript code in this page. * * Copyright (C) 2013 Ophir LOJKINE @@ -24,208 +24,222 @@ * @licend */ -(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 - // gives terrible results with the default parameters. In practice it - // 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 AUTO_FINGER_WHITEOUT = Tools.server_config.AUTO_FINGER_WHITEOUT; - var hasUsedStylus = false; - - //Indicates the id of the line the user is currently drawing or an empty string while the user is not drawing - var curLineId = "", - lastTime = performance.now(); //The time at which the last point was drawn - - //The data of the message that will be sent for every new point - function PointMessage(x, y) { - this.type = 'child'; - this.parent = curLineId; - this.x = x; - this.y = y; - } - - function handleAutoWhiteOut(evt) { - if (evt.touches && evt.touches[0] && evt.touches[0].touchType == "stylus") { - //When using stylus, switch back to the primary - if (hasUsedStylus && Tools.curTool.secondary.active) { - Tools.change("Pencil"); - } - //Remember if starting a line with a stylus - hasUsedStylus = true; - } - if (evt.touches && evt.touches[0] && evt.touches[0].touchType == "direct") { - //When used stylus and touched with a finger, switch to secondary - if (hasUsedStylus && !Tools.curTool.secondary.active) { - Tools.change("Pencil"); - } - } - } - - function startLine(x, y, evt) { - - //Prevent the press from being interpreted by the browser - evt.preventDefault(); - - if (AUTO_FINGER_WHITEOUT) handleAutoWhiteOut(evt); - - 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()), - }); - - //Immediatly add a point to the line - continueLine(x, y); - } - - function continueLine(x, y, evt) { - /*Wait 70ms before adding any point to the currently drawing line. +(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 + // gives terrible results with the default parameters. In practice it + // 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 AUTO_FINGER_WHITEOUT = Tools.server_config.AUTO_FINGER_WHITEOUT; + var hasUsedStylus = false; + + //Indicates the id of the line the user is currently drawing or an empty string while the user is not drawing + var curLineId = "", + lastTime = performance.now(); //The time at which the last point was drawn + + //The data of the message that will be sent for every new point + function PointMessage(x, y) { + this.type = "child"; + this.parent = curLineId; + this.x = x; + this.y = y; + } + + function handleAutoWhiteOut(evt) { + if (evt.touches && evt.touches[0] && evt.touches[0].touchType == "stylus") { + //When using stylus, switch back to the primary + if (hasUsedStylus && Tools.curTool.secondary.active) { + Tools.change("Pencil"); + } + //Remember if starting a line with a stylus + hasUsedStylus = true; + } + if (evt.touches && evt.touches[0] && evt.touches[0].touchType == "direct") { + //When used stylus and touched with a finger, switch to secondary + if (hasUsedStylus && !Tools.curTool.secondary.active) { + Tools.change("Pencil"); + } + } + } + + function startLine(x, y, evt) { + //Prevent the press from being interpreted by the browser + evt.preventDefault(); + + if (AUTO_FINGER_WHITEOUT) handleAutoWhiteOut(evt); + + 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(), + }); + + //Immediatly add a point to the line + continueLine(x, y); + } + + 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) { - Tools.drawAndSend(new PointMessage(x, y)); - lastTime = performance.now(); - } - if (evt) evt.preventDefault(); - } - - function stopLineAt(x, y) { - //Add a last point to the line - continueLine(x, y); - stopLine(); - } - - function stopLine() { - curLineId = ""; - } - - var renderingLine = {}; - function draw(data) { - Tools.drawingEvent = true; - switch (data.type) { - case "line": - renderingLine = createLine(data); - break; - case "child": - 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 - } - addPoint(line, data.x, data.y); - break; - case "endline": - //TODO? - break; - default: - console.error("Pencil: Draw instruction with unknown type. ", data); - break; - } - } - - var pathDataCache = {}; - function getPathData(line) { - var pathData = pathDataCache[line.id]; - if (!pathData) { - pathData = line.getPathData(); - pathDataCache[line.id] = pathData; - } - return pathData; - } - - var svg = Tools.svg; - - function addPoint(line, x, y) { - var pts = getPathData(line); - pts = wboPencilPoint(pts, x, y); - line.setPathData(pts); - } - - 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"); - 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); - Tools.drawingArea.appendChild(line); - return line; - } - - //Remember drawing and white-out sizes separately - var drawingSize = -1; - var whiteOutSize = -1; - - function restoreDrawingSize() { - whiteOutSize = Tools.getSize(); - if (drawingSize != -1) { - Tools.setSize(drawingSize); - } - } - - function restoreWhiteOutSize() { - drawingSize = Tools.getSize(); - if (whiteOutSize != -1) { - Tools.setSize(whiteOutSize); - } - } - - //Restore remembered size after switch - function toggleSize() { - if (pencilTool.secondary.active) { - restoreWhiteOutSize(); - } else { - restoreDrawingSize(); - } - } - - var pencilTool = { - "name": "Pencil", - "shortcut": "p", - "listeners": { - "press": startLine, - "move": continueLine, - "release": stopLineAt, - }, - "draw": draw, - "onstart": function(oldTool) { - //Reset stylus - hasUsedStylus = false; - }, - "secondary": { - "name": "White-out", - "icon": "tools/pencil/whiteout_tape.svg", - "active": false, - "switch": function() { - stopLine(); - toggleSize(); - }, - }, - "onstart": function() { - //When switching from another tool to white-out, restore white-out size - if (pencilTool.secondary.active) { - restoreWhiteOutSize(); - } - }, - "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", - }; - Tools.add(pencilTool); - + if ( + curLineId !== "" && + performance.now() - lastTime > MIN_PENCIL_INTERVAL_MS + ) { + Tools.drawAndSend(new PointMessage(x, y)); + lastTime = performance.now(); + } + if (evt) evt.preventDefault(); + } + + function stopLineAt(x, y) { + //Add a last point to the line + continueLine(x, y); + stopLine(); + } + + function stopLine() { + curLineId = ""; + } + + var renderingLine = {}; + function draw(data) { + Tools.drawingEvent = true; + switch (data.type) { + case "line": + renderingLine = createLine(data); + break; + case "child": + 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 + } + addPoint(line, data.x, data.y); + break; + case "endline": + //TODO? + break; + default: + console.error("Pencil: Draw instruction with unknown type. ", data); + break; + } + } + + var pathDataCache = {}; + function getPathData(line) { + var pathData = pathDataCache[line.id]; + if (!pathData) { + pathData = line.getPathData(); + pathDataCache[line.id] = pathData; + } + return pathData; + } + + var svg = Tools.svg; + + function addPoint(line, x, y) { + var pts = getPathData(line); + pts = wboPencilPoint(pts, x, y); + line.setPathData(pts); + } + + 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"); + 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, + ); + Tools.drawingArea.appendChild(line); + return line; + } + + //Remember drawing and white-out sizes separately + var drawingSize = -1; + var whiteOutSize = -1; + + function restoreDrawingSize() { + whiteOutSize = Tools.getSize(); + if (drawingSize != -1) { + Tools.setSize(drawingSize); + } + } + + function restoreWhiteOutSize() { + drawingSize = Tools.getSize(); + if (whiteOutSize != -1) { + Tools.setSize(whiteOutSize); + } + } + + //Restore remembered size after switch + function toggleSize() { + if (pencilTool.secondary.active) { + restoreWhiteOutSize(); + } else { + restoreDrawingSize(); + } + } + + var pencilTool = { + name: "Pencil", + shortcut: "p", + listeners: { + press: startLine, + move: continueLine, + release: stopLineAt, + }, + draw: draw, + onstart: function (oldTool) { + //Reset stylus + hasUsedStylus = false; + }, + secondary: { + name: "White-out", + icon: "tools/pencil/whiteout_tape.svg", + active: false, + switch: function () { + stopLine(); + toggleSize(); + }, + }, + onstart: function () { + //When switching from another tool to white-out, restore white-out size + if (pencilTool.secondary.active) { + restoreWhiteOutSize(); + } + }, + 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", + }; + Tools.add(pencilTool); })(); //End of code isolation diff --git a/client-data/tools/pencil/wbo_pencil_point.js b/client-data/tools/pencil/wbo_pencil_point.js index e8894c22..22b3f280 100644 --- a/client-data/tools/pencil/wbo_pencil_point.js +++ b/client-data/tools/pencil/wbo_pencil_point.js @@ -1,93 +1,91 @@ (function (global) { - "use strict"; + "use strict"; - function dist(x1, y1, x2, y2) { - //Returns the distance between (x1,y1) and (x2,y2) - return Math.hypot(x2 - x1, y2 - y1); - } + function dist(x1, y1, x2, y2) { + //Returns the distance between (x1,y1) and (x2,y2) + return Math.hypot(x2 - x1, y2 - y1); + } - /** - * Represents a single operation in an SVG path - * @param {string} type - * @param {number[]} values - */ - function PathDataPoint(type, values) { - this.type = type; - this.values = values; - } + /** + * Represents a single operation in an SVG path + * @param {string} type + * @param {number[]} values + */ + function PathDataPoint(type, values) { + this.type = type; + this.values = values; + } - /** - * Given the existing points in a path, add a new point to get a smoothly interpolated path - * @param {PathDataPoint[]} pts - * @param {number} x - * @param {number} y - */ - function wboPencilPoint(pts, x, y) { - // pts represents the points that are already in the line as a PathData - var nbr = pts.length; //The number of points already in the line - var npoint; - switch (nbr) { - case 0: //The first point in the line - //If there is no point, we have to start the line with a moveTo statement - pts.push(new PathDataPoint("M", [x, y])); - //Temporary first point so that clicks are shown and can be erased - npoint = new PathDataPoint("L", [x, y]); - break; - case 1: //This should never happen - // First point will be the move. Add Line of zero length ensure there are two points and fall through - pts.push(new PathDataPoint("L", [pts[0].values[0], pts[0].values[1]])); - // noinspection FallThroughInSwitchStatementJS - 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, - ]); - break; - default: //There are at least two points in the line - npoint = pencilExtrapolatePoints(pts, x, y); - } - if (npoint) pts.push(npoint); - return pts; + /** + * Given the existing points in a path, add a new point to get a smoothly interpolated path + * @param {PathDataPoint[]} pts + * @param {number} x + * @param {number} y + */ + function wboPencilPoint(pts, x, y) { + // pts represents the points that are already in the line as a PathData + var nbr = pts.length; //The number of points already in the line + var npoint; + switch (nbr) { + case 0: //The first point in the line + //If there is no point, we have to start the line with a moveTo statement + pts.push(new PathDataPoint("M", [x, y])); + //Temporary first point so that clicks are shown and can be erased + npoint = new PathDataPoint("L", [x, y]); + break; + case 1: //This should never happen + // First point will be the move. Add Line of zero length ensure there are two points and fall through + pts.push(new PathDataPoint("L", [pts[0].values[0], pts[0].values[1]])); + // noinspection FallThroughInSwitchStatementJS + 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, + ]); + break; + default: //There are at least two points in the line + npoint = pencilExtrapolatePoints(pts, x, y); } + if (npoint) pts.push(npoint); + return pts; + } - function pencilExtrapolatePoints(pts, x, y) { - //We add the new point, and smoothen the line - var ANGULARITY = 3; //The lower this number, the smoother the line - var prev_values = pts[pts.length - 1].values; // Previous point - var ante_values = pts[pts.length - 2].values; // Point before the previous one - var prev_x = prev_values[prev_values.length - 2]; - var prev_y = prev_values[prev_values.length - 1]; - var ante_x = ante_values[ante_values.length - 2]; - var ante_y = ante_values[ante_values.length - 1]; + function pencilExtrapolatePoints(pts, x, y) { + //We add the new point, and smoothen the line + var ANGULARITY = 3; //The lower this number, the smoother the line + var prev_values = pts[pts.length - 1].values; // Previous point + var ante_values = pts[pts.length - 2].values; // Point before the previous one + var prev_x = prev_values[prev_values.length - 2]; + var prev_y = prev_values[prev_values.length - 1]; + 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; - //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; + var vectx = x - ante_x, + vecty = y - ante_y; + var norm = Math.hypot(vectx, vecty); + var dist1 = dist(ante_x, ante_y, prev_x, prev_y) / norm, + dist2 = dist(x, y, prev_x, prev_y) / norm; + vectx /= ANGULARITY; + vecty /= ANGULARITY; + //Create 2 control points around the last point + var cx1 = prev_x - dist1 * vectx, + cy1 = prev_y - dist1 * vecty, //First control point + cx2 = prev_x + dist2 * vectx, + cy2 = prev_y + dist2 * vecty; //Second control point + prev_values[2] = cx1; + prev_values[3] = cy1; - var vectx = x - ante_x, - vecty = y - ante_y; - var norm = Math.hypot(vectx, vecty); - var dist1 = dist(ante_x, ante_y, prev_x, prev_y) / norm, - dist2 = dist(x, y, prev_x, prev_y) / norm; - vectx /= ANGULARITY; - vecty /= ANGULARITY; - //Create 2 control points around the last point - var cx1 = prev_x - dist1 * vectx, - cy1 = prev_y - dist1 * vecty, //First control point - cx2 = prev_x + dist2 * vectx, - cy2 = prev_y + dist2 * vecty; //Second control point - 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); \ No newline at end of file + global["wboPencilPoint"] = wboPencilPoint; +})(("object" === typeof module && module.exports) || window); diff --git a/client-data/tools/rect/rect.css b/client-data/tools/rect/rect.css index 88b704fe..4822db66 100755 --- a/client-data/tools/rect/rect.css +++ b/client-data/tools/rect/rect.css @@ -1,3 +1,3 @@ #drawingArea rect { - fill: none; + fill: none; } diff --git a/client-data/tools/rect/rect.js b/client-data/tools/rect/rect.js index 89fe151f..6bbebb19 100644 --- a/client-data/tools/rect/rect.js +++ b/client-data/tools/rect/rect.js @@ -24,137 +24,148 @@ * @licend */ -(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 - }, - lastTime = performance.now(); //The time at which the last point was drawn +(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, + }, + lastTime = performance.now(); //The time at which the last point was drawn - function start(x, y, evt) { + function start(x, y, evt) { + //Prevent the press from being interpreted by the browser + evt.preventDefault(); - //Prevent the press from being interpreted by the browser - evt.preventDefault(); + curId = Tools.generateUID("r"); //"r" for rectangle - 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, + }); - Tools.drawAndSend({ - 'type': 'rect', - 'id': curId, - 'color': Tools.getColor(), - 'size': Tools.getSize(), - 'opacity': Tools.getOpacity(), - 'x': x, - 'y': y, - 'x2': x, - 'y2': y - }); + curUpdate.id = curId; + curUpdate.x = x; + curUpdate.y = y; + } - curUpdate.id = curId; - curUpdate.x = x; - curUpdate.y = y; - } - - function move(x, y, evt) { - /*Wait 70ms before adding any point to the currently drawing shape. + function move(x, y, evt) { + /*Wait 70ms before adding any point to the currently drawing shape. This allows the animation to be smother*/ - if (curId !== "") { - if (rectangleTool.secondary.active) { - var dx = x - curUpdate.x; - var dy = y - curUpdate.y; - var d = Math.max(Math.abs(dx), Math.abs(dy)); - x = curUpdate.x + (dx > 0 ? d : -d); - y = curUpdate.y + (dy > 0 ? d : -d); - } - curUpdate['x2'] = x; curUpdate['y2'] = y; - if (performance.now() - lastTime > 70 || end) { - Tools.drawAndSend(curUpdate); - lastTime = performance.now(); - } else { - draw(curUpdate); - } - } - if (evt) evt.preventDefault(); - } - - function stop(x, y) { - //Add a last point to the shape - end = true; - move(x, y); - end = false; - curId = ""; - } + if (curId !== "") { + if (rectangleTool.secondary.active) { + var dx = x - curUpdate.x; + var dy = y - curUpdate.y; + var d = Math.max(Math.abs(dx), Math.abs(dy)); + x = curUpdate.x + (dx > 0 ? d : -d); + y = curUpdate.y + (dy > 0 ? d : -d); + } + curUpdate["x2"] = x; + curUpdate["y2"] = y; + if (performance.now() - lastTime > 70 || end) { + Tools.drawAndSend(curUpdate); + lastTime = performance.now(); + } else { + draw(curUpdate); + } + } + if (evt) evt.preventDefault(); + } - function draw(data) { - Tools.drawingEvent = true; - switch (data.type) { - case "rect": - createShape(data); - break; - case "update": - 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'] - }); - } - updateShape(shape, data); - break; - default: - console.error("Straight shape: Draw instruction with unknown type. ", data); - break; - } - } + function stop(x, y) { + //Add a last point to the shape + end = true; + move(x, y); + end = false; + curId = ""; + } - 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("rect"); - shape.id = data.id; - updateShape(shape, data); - //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); - Tools.drawingArea.appendChild(shape); - return shape; - } + function draw(data) { + Tools.drawingEvent = true; + switch (data.type) { + case "rect": + createShape(data); + break; + case "update": + 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"], + }); + } + updateShape(shape, data); + break; + default: + console.error( + "Straight shape: Draw instruction with unknown type. ", + data, + ); + break; + } + } - 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']); - } + 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("rect"); + shape.id = data.id; + updateShape(shape, data); + //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, + ); + Tools.drawingArea.appendChild(shape); + return shape; + } - var rectangleTool = { - "name": "Rectangle", - "shortcut": "r", - "listeners": { - "press": start, - "move": move, - "release": stop, - }, - "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" - }; - Tools.add(rectangleTool); + 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"]); + } + var rectangleTool = { + name: "Rectangle", + shortcut: "r", + listeners: { + press: start, + move: move, + release: stop, + }, + 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", + }; + Tools.add(rectangleTool); })(); //End of code isolation diff --git a/client-data/tools/text/text.css b/client-data/tools/text/text.css index 7fa937ca..7061640f 100644 --- a/client-data/tools/text/text.css +++ b/client-data/tools/text/text.css @@ -1,16 +1,15 @@ #textToolInput { - position:fixed; - top:-1000px; /*Hidden*/ - left: 80px; - width:500px; + position: fixed; + top: -1000px; /*Hidden*/ + left: 80px; + width: 500px; } #textToolInput:focus { - top: 5px; + 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; } - diff --git a/client-data/tools/text/text.js b/client-data/tools/text/text.js index 41e41288..27689740 100644 --- a/client-data/tools/text/text.js +++ b/client-data/tools/text/text.js @@ -1,7 +1,7 @@ /** * WHITEBOPHIR ********************************************************* - * @licstart The following is the entire license notice for the + * @licstart The following is the entire license notice for the * JavaScript code in this page. * * Copyright (C) 2013 Ophir LOJKINE @@ -24,196 +24,212 @@ * @licend */ -(function () { //Code isolation - var board = Tools.board; - - var input = document.createElement("input"); - input.id = "textToolInput"; - input.type = "text"; - 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 - }; - - var active = false; - - - function onStart() { - curText.oldSize = Tools.getSize(); - Tools.setSize(curText.rawSize); - } - - function onQuit() { - stopEdit(); - Tools.setSize(curText.oldSize); - } - - function clickHandler(x, y, evt, isTouchEvent) { - //if(document.querySelector("#menu").offsetWidth>Tools.menu_width+3) return; - if (evt.target === input) return; - if (evt.target.tagName === "text") { - editOldText(evt.target); - evt.preventDefault(); - return; - } - curText.rawSize = Tools.getSize(); - curText.size = parseInt(curText.rawSize * 1.5 + 12); - curText.opacity = Tools.getOpacity(); - curText.color = Tools.getColor(); - curText.x = x; - curText.y = y + curText.size / 2; - - stopEdit(); - startEdit(); - evt.preventDefault(); - } - - function editOldText(elem) { - 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; - - curText.x = x; - curText.y = y; - curText.sentText = elem.textContent; - curText.size = parseInt(elem.getAttribute("font-size")); - curText.opacity = parseFloat(elem.getAttribute("opacity")); - curText.color = elem.getAttribute("fill"); - startEdit(); - input.value = elem.textContent; - } - - function startEdit() { - 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 x = curText.x * Tools.scale - document.documentElement.scrollLeft; - if (x + 250 > clientW) { - x = Math.max(60, clientW - 260) - } - - 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); - input.addEventListener("blur", blur); - } - - function stopEdit() { - try { input.blur(); } catch (e) { /* Internet Explorer */ } - active = false; - blur(); - curText.id = 0; - curText.sentText = ""; - input.value = ""; - input.removeEventListener("keyup", textChangeHandler); - } - - function blur() { - if (active) return; - input.style.top = '-1000px'; - } - - function textChangeHandler(evt) { - if (evt.which === 13) { // enter - curText.y += 1.5 * curText.size; - stopEdit(); - startEdit(); - } else if (evt.which === 27) { // escape - stopEdit(); - } - if (performance.now() - curText.lastSending > 100) { - if (curText.sentText !== input.value) { - //If the user clicked where there was no text, then create a new text field - 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 - }) - } - Tools.drawAndSend({ - 'type': "update", - 'id': curText.id, - 'txt': input.value.slice(0, 280) - }); - curText.sentText = input.value; - curText.lastSending = performance.now(); - } - } else { - clearTimeout(curText.timeout); - curText.timeout = setTimeout(textChangeHandler, 500, evt); - } - } - - function draw(data, isLocal) { - Tools.drawingEvent = true; - switch (data.type) { - case "new": - createTextField(data); - break; - 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"); - return false; - } - updateText(textField, data.txt); - break; - default: - console.error("Text: Draw instruction with unknown type. ", data); - break; - } - } - - function updateText(textField, text) { - textField.textContent = text; - } - - function createTextField(fieldData) { - var elem = Tools.createSVGElement("text"); - elem.id = fieldData.id; - elem.setAttribute("x", fieldData.x); - 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); - 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, - }, - "onstart": onStart, - "onquit": onQuit, - "draw": draw, - "stylesheet": "tools/text/text.css", - "icon": "tools/text/icon.svg", - "mouseCursor": "text" - }); - +(function () { + //Code isolation + var board = Tools.board; + + var input = document.createElement("input"); + input.id = "textToolInput"; + input.type = "text"; + 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, + }; + + var active = false; + + function onStart() { + curText.oldSize = Tools.getSize(); + Tools.setSize(curText.rawSize); + } + + function onQuit() { + stopEdit(); + Tools.setSize(curText.oldSize); + } + + function clickHandler(x, y, evt, isTouchEvent) { + //if(document.querySelector("#menu").offsetWidth>Tools.menu_width+3) return; + if (evt.target === input) return; + if (evt.target.tagName === "text") { + editOldText(evt.target); + evt.preventDefault(); + return; + } + curText.rawSize = Tools.getSize(); + curText.size = parseInt(curText.rawSize * 1.5 + 12); + curText.opacity = Tools.getOpacity(); + curText.color = Tools.getColor(); + curText.x = x; + curText.y = y + curText.size / 2; + + stopEdit(); + startEdit(); + evt.preventDefault(); + } + + function editOldText(elem) { + 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; + + curText.x = x; + curText.y = y; + curText.sentText = elem.textContent; + curText.size = parseInt(elem.getAttribute("font-size")); + curText.opacity = parseFloat(elem.getAttribute("opacity")); + curText.color = elem.getAttribute("fill"); + startEdit(); + input.value = elem.textContent; + } + + function startEdit() { + 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 x = curText.x * Tools.scale - document.documentElement.scrollLeft; + if (x + 250 > clientW) { + x = Math.max(60, clientW - 260); + } + + 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); + input.addEventListener("blur", blur); + } + + function stopEdit() { + try { + input.blur(); + } catch (e) { + /* Internet Explorer */ + } + active = false; + blur(); + curText.id = 0; + curText.sentText = ""; + input.value = ""; + input.removeEventListener("keyup", textChangeHandler); + } + + function blur() { + if (active) return; + input.style.top = "-1000px"; + } + + function textChangeHandler(evt) { + if (evt.which === 13) { + // enter + curText.y += 1.5 * curText.size; + stopEdit(); + startEdit(); + } else if (evt.which === 27) { + // escape + stopEdit(); + } + if (performance.now() - curText.lastSending > 100) { + if (curText.sentText !== input.value) { + //If the user clicked where there was no text, then create a new text field + 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, + }); + } + Tools.drawAndSend({ + type: "update", + id: curText.id, + txt: input.value.slice(0, 280), + }); + curText.sentText = input.value; + curText.lastSending = performance.now(); + } + } else { + clearTimeout(curText.timeout); + curText.timeout = setTimeout(textChangeHandler, 500, evt); + } + } + + function draw(data, isLocal) { + Tools.drawingEvent = true; + switch (data.type) { + case "new": + createTextField(data); + break; + 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", + ); + return false; + } + updateText(textField, data.txt); + break; + default: + console.error("Text: Draw instruction with unknown type. ", data); + break; + } + } + + function updateText(textField, text) { + textField.textContent = text; + } + + function createTextField(fieldData) { + var elem = Tools.createSVGElement("text"); + elem.id = fieldData.id; + elem.setAttribute("x", fieldData.x); + 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, + ); + 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, + }, + onstart: onStart, + onquit: onQuit, + draw: draw, + stylesheet: "tools/text/text.css", + icon: "tools/text/icon.svg", + mouseCursor: "text", + }); })(); //End of code isolation diff --git a/client-data/tools/zoom/zoom.js b/client-data/tools/zoom/zoom.js index 742b4464..85c79b63 100644 --- a/client-data/tools/zoom/zoom.js +++ b/client-data/tools/zoom/zoom.js @@ -1,7 +1,7 @@ /** * WHITEBOPHIR ********************************************************* - * @licstart The following is the entire license notice for the + * @licstart The following is the entire license notice for the * JavaScript code in this page. * * Copyright (C) 2013 Ophir LOJKINE @@ -24,165 +24,182 @@ * @licend */ -(function () { //Code isolation - var ZOOM_FACTOR = .5; - var origin = { - scrollX: document.documentElement.scrollLeft, - scrollY: document.documentElement.scrollTop, - x: 0.0, - y: 0.0, - clientY: 0, - scale: 1.0 - }; - var moved = false, pressed = false; +(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, + }; + 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) - ); - } + 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), + ); + } - var animation = null; - function animate(scale) { - cancelAnimationFrame(animation); - animation = requestAnimationFrame(function () { - zoom(origin, scale); - }); - } + var animation = null; + function animate(scale) { + cancelAnimationFrame(animation); + animation = requestAnimationFrame(function () { + zoom(origin, scale); + }); + } - function setOrigin(x, y, evt, isTouchEvent) { - origin.scrollX = document.documentElement.scrollLeft; - origin.scrollY = document.documentElement.scrollTop; - origin.x = x; - origin.y = y; - origin.clientY = getClientY(evt, isTouchEvent); - origin.scale = Tools.getScale(); - } + function setOrigin(x, y, evt, isTouchEvent) { + origin.scrollX = document.documentElement.scrollLeft; + origin.scrollY = document.documentElement.scrollTop; + origin.x = x; + origin.y = y; + origin.clientY = getClientY(evt, isTouchEvent); + origin.scale = Tools.getScale(); + } - function press(x, y, evt, isTouchEvent) { - evt.preventDefault(); - setOrigin(x, y, evt, isTouchEvent); - moved = false; - pressed = true; - } + function press(x, y, evt, isTouchEvent) { + evt.preventDefault(); + setOrigin(x, y, evt, isTouchEvent); + moved = false; + pressed = true; + } - function move(x, y, evt, isTouchEvent) { - if (pressed) { - evt.preventDefault(); - var delta = getClientY(evt, isTouchEvent) - origin.clientY; - var scale = origin.scale * (1 + delta * ZOOM_FACTOR / 100); - if (Math.abs(delta) > 1) moved = true; - animation = animate(scale); - } + function move(x, y, evt, isTouchEvent) { + if (pressed) { + evt.preventDefault(); + var delta = getClientY(evt, isTouchEvent) - origin.clientY; + var scale = origin.scale * (1 + (delta * ZOOM_FACTOR) / 100); + if (Math.abs(delta) > 1) moved = true; + animation = animate(scale); } + } - 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; - if (!evt.ctrlKey) { - // zoom - var scale = Tools.getScale(); - var x = evt.pageX / scale; - var y = evt.pageY / scale; - setOrigin(x, y, evt, false); - animate((1 - deltaY / 800) * Tools.getScale()); - } else if (evt.altKey) { - // make finer changes if shift is being held - var change = evt.shiftKey ? 1 : 5; - // change tool size - Tools.setSize(Tools.getSize() - deltaY / 100 * change); - } else if (evt.shiftKey) { - // scroll horizontally - window.scrollTo(document.documentElement.scrollLeft + deltaY, document.documentElement.scrollTop + deltaX); - } else { - // regular scrolling - window.scrollTo(document.documentElement.scrollLeft + deltaX, document.documentElement.scrollTop + deltaY); - } + 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; + if (!evt.ctrlKey) { + // zoom + var scale = Tools.getScale(); + var x = evt.pageX / scale; + var y = evt.pageY / scale; + setOrigin(x, y, evt, false); + animate((1 - deltaY / 800) * Tools.getScale()); + } else if (evt.altKey) { + // make finer changes if shift is being held + var change = evt.shiftKey ? 1 : 5; + // change tool size + Tools.setSize(Tools.getSize() - (deltaY / 100) * change); + } else if (evt.shiftKey) { + // scroll horizontally + window.scrollTo( + document.documentElement.scrollLeft + deltaY, + document.documentElement.scrollTop + deltaX, + ); + } else { + // regular scrolling + window.scrollTo( + document.documentElement.scrollLeft + deltaX, + document.documentElement.scrollTop + deltaY, + ); } - Tools.board.addEventListener("wheel", onwheel, { passive: false }); + } + Tools.board.addEventListener("wheel", onwheel, { passive: false }); - 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, - dx = x0 - x1, - dy = y0 - y1; - var x = (touches[0].pageX + touches[1].pageX) / 2 / Tools.getScale(), - y = (touches[0].pageY + touches[1].pageY) / 2 / Tools.getScale(); - var distance = Math.sqrt(dx * dx + dy * dy); - if (!pressed) { - pressed = true; - setOrigin(x, y, evt, true); - origin.distance = distance; - } else { - var delta = distance - origin.distance; - var scale = origin.scale * (1 + delta * ZOOM_FACTOR / 100); - animate(scale); - } + 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, + dx = x0 - x1, + dy = y0 - y1; + var x = (touches[0].pageX + touches[1].pageX) / 2 / Tools.getScale(), + y = (touches[0].pageY + touches[1].pageY) / 2 / Tools.getScale(); + var distance = Math.sqrt(dx * dx + dy * dy); + if (!pressed) { + pressed = true; + setOrigin(x, y, evt, true); + origin.distance = distance; + } else { + var delta = distance - origin.distance; + var scale = origin.scale * (1 + (delta * ZOOM_FACTOR) / 100); + animate(scale); } - }, { passive: true }); - function touchend() { - pressed = false; - } - Tools.board.addEventListener("touchend", touchend); - Tools.board.addEventListener("touchcancel", touchend); + } + }, + { passive: true }, + ); + function touchend() { + pressed = false; + } + Tools.board.addEventListener("touchend", touchend); + Tools.board.addEventListener("touchcancel", touchend); - function release(x, y, evt, isTouchEvent) { - if (pressed && !moved) { - var delta = (evt.shiftKey === true) ? -1 : 1; - var scale = Tools.getScale() * (1 + delta * ZOOM_FACTOR); - zoom(origin, scale); - } - pressed = false; + function release(x, y, evt, isTouchEvent) { + if (pressed && !moved) { + var delta = evt.shiftKey === true ? -1 : 1; + var scale = Tools.getScale() * (1 + delta * ZOOM_FACTOR); + zoom(origin, scale); } + pressed = false; + } - function key(down) { - return function (evt) { - if (evt.key === "Shift") { - Tools.svg.style.cursor = "zoom-" + (down ? "out" : "in"); - } - } - } + function key(down) { + return function (evt) { + if (evt.key === "Shift") { + Tools.svg.style.cursor = "zoom-" + (down ? "out" : "in"); + } + }; + } - function getClientY(evt, isTouchEvent) { - return isTouchEvent ? evt.changedTouches[0].clientY : evt.clientY; - } + function getClientY(evt, isTouchEvent) { + return isTouchEvent ? evt.changedTouches[0].clientY : evt.clientY; + } - var keydown = key(true); - var keyup = key(false); + var keydown = key(true); + var keyup = key(false); - function onstart() { - window.addEventListener("keydown", keydown); - window.addEventListener("keyup", keyup); - } - function onquit() { - window.removeEventListener("keydown", keydown); - window.removeEventListener("keyup", keyup); - } + function onstart() { + window.addEventListener("keydown", keydown); + window.addEventListener("keyup", keyup); + } + function onquit() { + window.removeEventListener("keydown", keydown); + window.removeEventListener("keyup", keyup); + } - var zoomTool = { - "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, - }; - Tools.add(zoomTool); + var zoomTool = { + 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, + }; + Tools.add(zoomTool); })(); //End of code isolation diff --git a/docker-compose.yml b/docker-compose.yml index b016a4c1..f9babbe4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 - diff --git a/nightwatch.conf.js b/nightwatch.conf.js index 3b29d0ab..ef5c6a74 100644 --- a/nightwatch.conf.js +++ b/nightwatch.conf.js @@ -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) {} } diff --git a/package-lock.json b/package-lock.json index 1f1656ee..f1bad2c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,14 +13,27 @@ "async-mutex": "^0.3.1", "handlebars": "^4.7.7", "jsonwebtoken": "^9.0.0", + "minitpl": "^0.0.3", "polyfill-library": "^3.107.1", "serve-static": "^1.14.1", "socket.io": "^4", + "socket.io-client": "^4.7.2", "statsd-client": "^0.4.7" }, "devDependencies": { + "eslint": "^8.54.0", "geckodriver": "^4.2.1", - "nightwatch": "^3.2.1" + "nightwatch": "^3.2.1", + "prettier": "3.1.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@assemblyscript/loader": { @@ -319,6 +332,62 @@ "integrity": "sha512-N43uWud8ZXuVjza423T9ZCIJsaZhFekmakt7S9bvogTxqdVGbRobjR663s0+uW0Rz9e+Pa8I6jUuWtoBLQD2Mw==", "dev": true }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@financial-times/polyfill-useragent-normaliser": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/@financial-times/polyfill-useragent-normaliser/-/polyfill-useragent-normaliser-1.10.2.tgz", @@ -337,6 +406,39 @@ "resolved": "https://registry.npmjs.org/@financial-times/useragent_parser/-/useragent_parser-1.6.3.tgz", "integrity": "sha512-TlQiXt/vS5ZwY0V3salvlyQzIzMGZEyw9inmJA25A8heL2kBVENbToiEc64R6ETNf5YHa2lwnc2I7iNHP9SqeQ==" }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, "node_modules/@nightwatch/chai": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@nightwatch/chai/-/chai-5.0.2.tgz", @@ -370,6 +472,41 @@ "archiver": "^5.3.1" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -450,6 +587,12 @@ "@types/node": "*" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@wdio/logger": { "version": "8.16.17", "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.16.17.tgz", @@ -510,6 +653,15 @@ "acorn-walk": "^8.0.2" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", @@ -531,6 +683,22 @@ "node": ">= 14" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -977,6 +1145,15 @@ "node": ">=0.2.0" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -1305,6 +1482,41 @@ "node": ">= 10" } }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/cssstyle": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", @@ -1386,6 +1598,12 @@ "node": ">=6" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -1463,6 +1681,18 @@ "node": ">=0.3.1" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -1594,6 +1824,38 @@ "node": ">=10.2.0" } }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/engine.io-parser": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", @@ -1696,6 +1958,179 @@ "source-map": "~0.6.1" } }, + "node_modules/eslint": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -1709,6 +2144,30 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -1750,12 +2209,39 @@ "node >=0.6.0" ] }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "dev": true }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -1794,6 +2280,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -1861,6 +2359,26 @@ "flat": "cli.js" } }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -2106,11 +2624,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -2263,12 +2802,55 @@ } ] }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "dev": true }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -2609,6 +3191,24 @@ "node": ">= 6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -2703,6 +3303,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/knuth-shuffle-seeded": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz", @@ -2754,6 +3363,19 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -3153,6 +3775,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minitpl": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/minitpl/-/minitpl-0.0.3.tgz", + "integrity": "sha512-8+ChEb7JN7p8ObzAl9bzNVbA7rZC+9VNPxBIYr6bKGDKd4dnsZyWQPorLAtAb62u/jz4UUXnx5GQHg99LCib5w==", + "bin": { + "minitpl": "bin/minitpl" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -3333,6 +3963,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -3691,6 +4327,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -3811,6 +4464,18 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -3861,6 +4526,15 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -3914,6 +4588,30 @@ "node": ">=8" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "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", @@ -3970,6 +4668,26 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/queue-tick": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", @@ -4137,6 +4855,16 @@ "node": ">=8" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -4158,6 +4886,29 @@ "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", "dev": true }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4304,6 +5055,27 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -4355,6 +5127,20 @@ } } }, + "node_modules/socket.io-client": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", + "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -4603,6 +5389,12 @@ "node": ">=6" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -4751,6 +5543,18 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -4877,6 +5681,15 @@ "tslib": "^2.0.3" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -5129,6 +5942,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -5282,6 +6103,12 @@ } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, "@assemblyscript/loader": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", @@ -5528,6 +6355,44 @@ "integrity": "sha512-N43uWud8ZXuVjza423T9ZCIJsaZhFekmakt7S9bvogTxqdVGbRobjR663s0+uW0Rz9e+Pa8I6jUuWtoBLQD2Mw==", "dev": true }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@eslint/js": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "dev": true + }, "@financial-times/polyfill-useragent-normaliser": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/@financial-times/polyfill-useragent-normaliser/-/polyfill-useragent-normaliser-1.10.2.tgz", @@ -5542,6 +6407,29 @@ "resolved": "https://registry.npmjs.org/@financial-times/useragent_parser/-/useragent_parser-1.6.3.tgz", "integrity": "sha512-TlQiXt/vS5ZwY0V3salvlyQzIzMGZEyw9inmJA25A8heL2kBVENbToiEc64R6ETNf5YHa2lwnc2I7iNHP9SqeQ==" }, + "@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, "@nightwatch/chai": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@nightwatch/chai/-/chai-5.0.2.tgz", @@ -5562,13 +6450,39 @@ "integrity": "sha512-GEBeGoXVmTYPtNC4Yq34vfgxf6mlFyEagxpsfH18Qe5BvctF2rprX+wI5dKBm9p5IqHo6ZOcXHCufOeP3cjuOw==", "dev": true }, - "@nightwatch/nightwatch-inspector": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@nightwatch/nightwatch-inspector/-/nightwatch-inspector-1.0.1.tgz", - "integrity": "sha512-/ax11EOB4eJXT5VioMztcalbCtsNeuFn6icfT75qPLBmkxLvThePSfyGTys+t9AULUR0ug0wMDMiLV1Oy586Fg==", + "@nightwatch/nightwatch-inspector": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@nightwatch/nightwatch-inspector/-/nightwatch-inspector-1.0.1.tgz", + "integrity": "sha512-/ax11EOB4eJXT5VioMztcalbCtsNeuFn6icfT75qPLBmkxLvThePSfyGTys+t9AULUR0ug0wMDMiLV1Oy586Fg==", + "dev": true, + "requires": { + "archiver": "^5.3.1" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "requires": { - "archiver": "^5.3.1" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" } }, "@socket.io/component-emitter": { @@ -5645,6 +6559,12 @@ "@types/node": "*" } }, + "@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "@wdio/logger": { "version": "8.16.17", "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.16.17.tgz", @@ -5693,6 +6613,13 @@ "acorn-walk": "^8.0.2" } }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, "acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", @@ -5708,6 +6635,18 @@ "debug": "^4.3.4" } }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -6055,6 +6994,12 @@ "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", "dev": true }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -6293,6 +7238,34 @@ "readable-stream": "^3.4.0" } }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "cssstyle": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", @@ -6348,6 +7321,12 @@ "type-detect": "^4.0.0" } }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -6403,6 +7382,15 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -6526,6 +7514,26 @@ } } }, + "engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + }, + "dependencies": { + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "requires": {} + } + } + }, "engine.io-parser": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", @@ -6581,12 +7589,154 @@ "source-map": "~0.6.1" } }, + "eslint": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, + "espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -6616,12 +7766,39 @@ "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", "dev": true }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, "fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "dev": true }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -6641,6 +7818,15 @@ "escape-string-regexp": "^1.0.5" } }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -6695,6 +7881,23 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, + "flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -6888,11 +8091,26 @@ "ini": "2.0.0" } }, + "globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -6999,12 +8217,42 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true }, + "ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true + }, "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "dev": true }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -7248,6 +8496,24 @@ } } }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -7338,6 +8604,15 @@ "safe-buffer": "^5.0.1" } }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, "knuth-shuffle-seeded": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz", @@ -7388,6 +8663,16 @@ } } }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -7731,6 +9016,11 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, + "minitpl": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/minitpl/-/minitpl-0.0.3.tgz", + "integrity": "sha512-8+ChEb7JN7p8ObzAl9bzNVbA7rZC+9VNPxBIYr6bKGDKd4dnsZyWQPorLAtAb62u/jz4UUXnx5GQHg99LCib5w==" + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -7873,6 +9163,12 @@ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -8140,6 +9436,20 @@ "is-wsl": "^2.2.0" } }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + } + }, "ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -8226,6 +9536,15 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -8260,6 +9579,12 @@ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -8299,6 +9624,18 @@ "toposort": "^2.0.2" } }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "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", @@ -8349,6 +9686,12 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "queue-tick": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", @@ -8488,6 +9831,12 @@ "signal-exit": "^3.0.2" } }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -8503,6 +9852,15 @@ "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", "dev": true }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -8621,6 +9979,21 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -8657,6 +10030,17 @@ } } }, + "socket.io-client": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", + "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + } + }, "socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -8855,6 +10239,12 @@ "readable-stream": "^3.1.1" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -8983,6 +10373,15 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -9083,6 +10482,15 @@ "tslib": "^2.0.3" } }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -9269,6 +10677,11 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index cf73bacc..d913e67a 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,14 @@ "polyfill-library": "^3.107.1", "serve-static": "^1.14.1", "socket.io": "^4", + "socket.io-client": "^4.7.2", "statsd-client": "^0.4.7" }, "scripts": { "start": "node ./server/server.js", - "test": "nightwatch tests && nightwatch tests --env jwt" + "test": "nightwatch tests && nightwatch tests --env jwt", + "prettier": "prettier . --write", + "lint": "npx eslint ." }, "main": "./server/server.js", "repository": { @@ -27,7 +30,9 @@ "url": "http://github.com/lovasoa/whitebophir.git" }, "devDependencies": { + "eslint": "^8.54.0", "geckodriver": "^4.2.1", - "nightwatch": "^3.2.1" + "nightwatch": "^3.2.1", + "prettier": "3.1.0" } } diff --git a/server/boardData.js b/server/boardData.js index 2ac11e12..a1f6c0a3 100644 --- a/server/boardData.js +++ b/server/boardData.js @@ -46,7 +46,7 @@ class BoardData { this.board = {}; this.file = path.join( config.HISTORY_DIR, - "board-" + encodeURIComponent(name) + ".json" + "board-" + encodeURIComponent(name) + ".json", ); this.lastSaveDate = Date.now(); this.users = new Set(); @@ -174,7 +174,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"); diff --git a/server/check_output_directory.js b/server/check_output_directory.js index f4023e10..106ee85f 100644 --- a/server/check_output_directory.js +++ b/server/check_output_directory.js @@ -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; - }) + }), ); } } @@ -66,8 +67,8 @@ function check_output_directory(directory) { if (error) { 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}".` + `\nThe history directory can be configured with the environment variable WBO_HISTORY_DIR. ` + + `It is currently set to "${directory}".`, ); process.exit(1); } diff --git a/server/configuration.js b/server/configuration.js index 16d42d1f..30e6ed55 100644 --- a/server/configuration.js +++ b/server/configuration.js @@ -43,22 +43,23 @@ 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. */ AUTO_FINGER_WHITEOUT: process.env["AUTO_FINGER_WHITEOUT"] !== "disabled", - /** If this variable is set, it should point to a statsd listener that will + /** If this variable is set, it should point to a statsd listener that will * receive WBO's monitoring information. * example: udp://127.0.0.1 - */ + */ STATSD_URL: process.env["STATSD_URL"], /** Secret key for jwt */ - 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"]), + 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"], }; diff --git a/server/createSVG.js b/server/createSVG.js index 5ce08210..c9a375a0 100644 --- a/server/createSVG.js +++ b/server/createSVG.js @@ -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( '" + "]]>", ); 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(""); } diff --git a/server/jwtBoardnameAuth.js b/server/jwtBoardnameAuth.js index e68c94ce..4bbbc395 100644 --- a/server/jwtBoardnameAuth.js +++ b/server/jwtBoardnameAuth.js @@ -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. @@ -37,15 +37,15 @@ config = require("./configuration.js"), */ function checkBoardnameInToken(url, boardNameIn) { - var token = url.searchParams.get("token"); - if (roleInBoard(token, boardNameIn) === 'forbidden') { - throw new Error("Acess Forbidden"); - } + var token = url.searchParams.get("token"); + 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} + let [_, role_name, board_name] = role.match(/^([^:]*):?(.*)$/); + return { role_name, board_name }; } /** @@ -56,44 +56,44 @@ function parse_role(role) { @returns {string} "moderator"|"editor"|"forbidden" */ function roleInBoard(token, board = null) { - if (config.AUTH_SECRET_KEY != "") { - if (!token) { - throw new Error("No token provided"); - } - var payload = jsonwebtoken.verify(token, config.AUTH_SECRET_KEY); + if (config.AUTH_SECRET_KEY != "") { + if (!token) { + throw new Error("No token provided"); + } + var payload = jsonwebtoken.verify(token, config.AUTH_SECRET_KEY); - var roles = payload.roles; - var oneHasBoardName = false; - var oneHasModerator = false; + var roles = payload.roles; + var oneHasBoardName = false; + var oneHasModerator = false; - if (roles) { - for (var line of roles) { - var role = parse_role(line); + if (roles) { + for (var line of roles) { + var role = parse_role(line); - if (role.board_name !== '') { - oneHasBoardName = true; - } - if (role.role_name === "moderator") { - oneHasModerator = true; - } - if (role.board_name === board) { - return role.role_name; - } - } - if ((!board && oneHasModerator) || !oneHasBoardName) { - if (oneHasModerator) { - return "moderator"; - } else { - return "editor"; - } - } - return "forbidden"; + if (role.board_name !== "") { + oneHasBoardName = true; + } + if (role.role_name === "moderator") { + oneHasModerator = true; + } + if (role.board_name === board) { + return role.role_name; + } + } + if ((!board && oneHasModerator) || !oneHasBoardName) { + if (oneHasModerator) { + return "moderator"; } else { - return "editor"; + return "editor"; } + } + return "forbidden"; } else { - return "editor"; + return "editor"; } + } else { + return "editor"; + } } -module.exports = {checkBoardnameInToken, roleInBoard}; +module.exports = { checkBoardnameInToken, roleInBoard }; diff --git a/server/jwtauth.js b/server/jwtauth.js index 4678b760..e9ee127e 100644 --- a/server/jwtauth.js +++ b/server/jwtauth.js @@ -1,7 +1,7 @@ /** * WHITEBOPHIR ********************************************************* - * @licstart The following is the entire license notice for the + * @licstart The following is the entire license notice for the * JavaScript code in this page. * * Copyright (C) 2013 Ophir LOJKINE @@ -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 }; diff --git a/server/server.js b/server/server.js index 4bcca78d..2c99f8bb 100644 --- a/server/server.js +++ b/server/server.js @@ -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"), crypto = require("crypto"), @@ -11,7 +11,7 @@ var app = require("http").createServer(handler), polyfillLibrary = require("polyfill-library"), check_output_directory = require("./check_output_directory.js"), jwtauth = require("./jwtauth.js"); - jwtBoardName = require("./jwtBoardnameAuth.js"); +jwtBoardName = require("./jwtBoardnameAuth.js"); var MIN_NODE_VERSION = 10.0; @@ -21,7 +21,7 @@ if (parseFloat(process.versions.node) < MIN_NODE_VERSION) { process.version + ", wbo requires at least " + MIN_NODE_VERSION + - " !!!" + " !!!", ); } @@ -82,10 +82,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"), ); /** @@ -102,16 +102,16 @@ function validateBoardName(boardName) { * @type {import('http').RequestListener} */ function handleRequest(request, response) { - var parsedUrl = new URL(request.url, 'http://wbo/'); + var parsedUrl = new URL(request.url, "http://wbo/"); var parts = parsedUrl.pathname.split("/"); 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); } @@ -137,51 +137,51 @@ function handleRequest(request, response) { break; case "download": - var boardName = validateBoardName(parts[1]), - history_file = path.join( - config.HISTORY_DIR, - "board-" + boardName + ".json" - ); - jwtBoardName.checkBoardnameInToken(parsedUrl, boardName); - if (parts.length > 2 && /^[0-9A-Za-z.\-]+$/.test(parts[2])) { - history_file += "." + parts[2] + ".bak"; - } - log("download", { file: history_file }); - fs.readFile(history_file, function (err, data) { - if (err) return serveError(request, response)(err); - response.writeHead(200, { - "Content-Type": "application/json", - "Content-Disposition": 'attachment; filename="' + boardName + '.wbo"', - "Content-Length": data.length, - }); - response.end(data); + var boardName = validateBoardName(parts[1]), + history_file = path.join( + config.HISTORY_DIR, + "board-" + boardName + ".json", + ); + jwtBoardName.checkBoardnameInToken(parsedUrl, boardName); + if (parts.length > 2 && /^[0-9A-Za-z.\-]+$/.test(parts[2])) { + history_file += "." + parts[2] + ".bak"; + } + log("download", { file: history_file }); + fs.readFile(history_file, function (err, data) { + if (err) return serveError(request, response)(err); + response.writeHead(200, { + "Content-Type": "application/json", + "Content-Disposition": 'attachment; filename="' + boardName + '.wbo"', + "Content-Length": data.length, }); + response.end(data); + }); break; case "export": case "preview": - var boardName = validateBoardName(parts[1]), - history_file = path.join( - config.HISTORY_DIR, - "board-" + boardName + ".json" - ); - jwtBoardName.checkBoardnameInToken(parsedUrl, boardName); - response.writeHead(200, { - "Content-Type": "image/svg+xml", - "Content-Security-Policy": CSP, - "Cache-Control": "public, max-age=30", + var boardName = validateBoardName(parts[1]), + history_file = path.join( + config.HISTORY_DIR, + "board-" + boardName + ".json", + ); + jwtBoardName.checkBoardnameInToken(parsedUrl, boardName); + response.writeHead(200, { + "Content-Type": "image/svg+xml", + "Content-Security-Policy": CSP, + "Cache-Control": "public, max-age=30", + }); + var t = Date.now(); + createSVG + .renderBoard(history_file, response) + .then(function () { + log("preview", { board: boardName, time: Date.now() - t }); + response.end(); + }) + .catch(function (err) { + log("error", { error: err.toString(), stack: err.stack }); + response.end("Sorry, an error occured"); }); - var t = Date.now(); - createSVG - .renderBoard(history_file, response) - .then(function () { - log("preview", { board: boardName, time: Date.now() - t }); - response.end(); - }) - .catch(function (err) { - log("error", { error: err.toString(), stack: err.stack }); - response.end("Sorry, an error occured"); - }); break; case "random": @@ -213,7 +213,7 @@ 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"); @@ -224,10 +224,11 @@ 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: diff --git a/server/sockets.js b/server/sockets.js index 58d9c656..7e9735ee 100644 --- a/server/sockets.js +++ b/server/sockets.js @@ -32,12 +32,17 @@ function startIO(app) { 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")); - next(); - }) + 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")); } @@ -88,7 +93,7 @@ function handleSocketConnection(socket) { "error", noFail(function onSocketError(error) { log("ERROR", error); - }) + }), ); socket.on("getboard", async function onGetBoard(name) { @@ -148,7 +153,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) { diff --git a/server/templating.js b/server/templating.js index 46c82757..4eb1c6c8 100644 --- a/server/templating.js +++ b/server/templating.js @@ -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); diff --git a/tests/integration.js b/tests/integration.js index bba101fb..caf110c1 100644 --- a/tests/integration.js +++ b/tests/integration.js @@ -2,131 +2,216 @@ 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; async function beforeEach(browser, done) { - 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) { - process.env["AUTH_SECRET_KEY"] = "test"; - tokenQuery = "token=" + browser.globals.token; - } - console.log("Launching WBO in " + data_path); - wbo = require("../server/server.js"); - done(); + 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) { + process.env["AUTH_SECRET_KEY"] = "test"; + tokenQuery = "token=" + browser.globals.token; + } + console.log("Launching WBO in " + data_path); + wbo = require("../server/server.js"); + done(); } async function afterEach(browser, done) { - wbo.close(); - done(); + wbo.close(); + done(); } function testPencil(browser) { - 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); }); - } - // A straight path with just two points - Tools.setColor('#123456'); - Tools.curTool.listeners.press(100, 200, new Event("mousedown")); - await sleep(80); - Tools.curTool.listeners.release(300, 400, new Event("mouseup")); + 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); + }); + } + // A straight path with just two points + 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.curTool.listeners.press(0, 0, new Event("mousedown")); - await sleep(80); - Tools.curTool.listeners.move(90, 120, new Event("mousemove")); - await sleep(80); - 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']") - .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() + // A line with three points that form an "U" shape + await sleep(80); + Tools.setColor("#abcdef"); + Tools.curTool.listeners.press(0, 0, new Event("mousedown")); + await sleep(80); + Tools.curTool.listeners.move(90, 120, new Event("mousemove")); + await sleep(80); + 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']", + ) + .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(); } function testCircle(browser) { - return browser - .click('#toolID-Ellipse') - .executeAsync(function (done) { - Tools.setColor('#112233'); - Tools.curTool.listeners.press(200, 400, new Event("mousedown")); - setTimeout(() => { - const evt = new Event("mousemove"); - evt.shiftKey = true; - Tools.curTool.listeners.move(0, 0, evt); - done(); - }, 100); - }) - .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 + return browser + .click("#toolID-Ellipse") + .executeAsync(function (done) { + Tools.setColor("#112233"); + Tools.curTool.listeners.press(200, 400, new Event("mousedown")); + setTimeout(() => { + const evt = new Event("mousemove"); + evt.shiftKey = true; + Tools.curTool.listeners.move(0, 0, evt); + done(); + }, 100); + }) + .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 } function testCursor(browser) { - return browser - .execute(function (done) { - Tools.setColor('#456123'); // Move the cursor over the board - var e = new Event("mousemove"); - e.pageX = 150; - e.pageY = 200; - Tools.board.dispatchEvent(e) - }) - .assert.cssProperty("#cursor-me", "transform", "matrix(1, 0, 0, 1, 150, 200)") - .assert.attributeEquals("#cursor-me", "fill", "#456123") + return browser + .execute(function (done) { + Tools.setColor("#456123"); // Move the cursor over the board + var e = new Event("mousemove"); + e.pageX = 150; + e.pageY = 200; + Tools.board.dispatchEvent(e); + }) + .assert.cssProperty( + "#cursor-me", + "transform", + "matrix(1, 0, 0, 1, 150, 200)", + ) + .assert.attributeEquals("#cursor-me", "fill", "#456123"); } function testBoard(browser) { - var page = browser.url(SERVER + '/boards/anonymous?lang=fr&' + tokenQuery) - .waitForElementVisible('.tool[title ~= Crayon]') // pencil - page = testPencil(page); - page = testCircle(page); - page = testCursor(page); + var page = browser + .url(SERVER + "/boards/anonymous?lang=fr&" + tokenQuery) + .waitForElementVisible(".tool[title ~= Crayon]"); // pencil + 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) { - //has moderator jwt and no board name - 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'); - //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'); - //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'); - //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') - //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'); - //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'); - //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'); - } - page.end(); + // 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) { + //has moderator jwt and no board name + 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"); + //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"); + //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"); + //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"); + //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"); + //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"); + //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"); + } + page.end(); } module.exports = { beforeEach, testBoard, afterEach };