Merge pull request #51 from FloChehab/style/setup_prettier

Features: auto-formating and CI to ckeck formating and docker build
This commit is contained in:
Cracker 2020-05-09 18:10:55 +02:00 committed by GitHub
commit 69d7ebbe34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2081 additions and 894 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[{*.js,*.css,*.html}]
indent_size = 4

18
.github/workflows/build-docker.yml vendored Normal file
View File

@ -0,0 +1,18 @@
# This workflow will do clean build of the docker image
name: Docker Image CI (also tests build)
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build the Docker image
run: docker build . --file Dockerfile --tag rofl256/whiteboard:$(date +%s)

21
.github/workflows/linting-code.yml vendored Normal file
View File

@ -0,0 +1,21 @@
# This workflow will do a clean install of node dependencies and check the code style
name: Linting code CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- run: npm ci
- run: npm run style

3
.prettierrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"printWidth": 100
}

151
README.md
View File

@ -1,27 +1,33 @@
# whiteboard
This is a lightweight NodeJS collaborative Whiteboard/Sketchboard witch can easily be customized...
![start](./doc/start.png)
## Demowhiteboard ##
## Demowhiteboard
[HERE](https://cloud13.de/testwhiteboard/) (Reset every night)
## Some Features
* Shows remote user cursors while drawing
* Undo / Redo function for each user
* Drag+Drop / Copy+Paste Images or PDFs from PC and Browsers
* Resize, Move & Draw Images to Canvas or Background
* Write text
* Save Whiteboard to Image and JSON
* Draw angle lines by pressing "shift" while drawing (with line tool)
* Draw square by pressing "shift" while drawing (with rectangle tool)
* Indicator that shows the smallest screen participating
* Keybindings for ALL the functions
* Working on PC, Tablet & Mobile
- Shows remote user cursors while drawing
- Undo / Redo function for each user
- Drag+Drop / Copy+Paste Images or PDFs from PC and Browsers
- Resize, Move & Draw Images to Canvas or Background
- Write text
- Save Whiteboard to Image and JSON
- Draw angle lines by pressing "shift" while drawing (with line tool)
- Draw square by pressing "shift" while drawing (with rectangle tool)
- Indicator that shows the smallest screen participating
- Keybindings for ALL the functions
- Working on PC, Tablet & Mobile
## Install the App
You can run this app with and without docker
### Without Docker
1. install the latest NodeJs
2. Clone the app
3. Run `npm ci` inside the folder
@ -29,6 +35,7 @@ You can run this app with and without docker
5. Surf to http://YOURIP:8080
### With Docker
1. `docker run -d -p 8080:8080 rofl256/whiteboard`
2. Surf to http://YOURIP:8080
@ -37,63 +44,66 @@ You can run this app with and without docker
After you have installed the app, run `npm run start:dev` to start the backend and a frontend development server. The website will be accessible on http://locahost:8080.
## Default keyboard shortcuts
Use keyboard shortcuts to become more productive while using Whiteboard.
They are especially useful if you work with interactive displays such as XP-Pen Artist, Huion Kamvas and Wacom Cintiq. These devices have quick buttons (6-8 buttons and scrolling). By default, the buttons on these displays are mapped to standard Photoshop keyboard shortcuts. Keys can be configured to function effectively in other software.
The following are predefined shortcuts that you can override in the file [./src/js/keybinds.js](./src/js/keybinds.js)
Result | Windows and Linux | macOS
------ | -------------------- | -------
Clear the whiteboard | Ctrl + Shift + Z | Command + Shift + Z
Undo your last step | Ctrl + Z | Command + Z
Redo your last undo | Ctrl + Y | Command + Y
Select an area | Ctrl + X | Command + X
Take the mouse | Ctrl + M | Command + M
Take the pen | Ctrl + P | Command + P
Draw a line | Ctrl + L | Command + L
Draw a rectangle | Ctrl + R | Command + R
Draw a circle | Ctrl + C | Command + C
Toggle between line, rectangle and circle | Ctrl + Shift + F | Command + Shift + F
Toggle between pen and eraser | Ctrl + Shift + X | Command + Shift + X
Toggle between main clolors (black, blue, green, yellow and red) | Ctrl + Shift + R | Command + Shift + R
Write text | Ctrl + A | Command + A
Take the eraser | Ctrl + E | Command + E
Increase thickness | Ctrl + Up Arrow | Command + Up Arrow
Decrease thickness | Ctrl + Down Arrow | Command + Down Arrow
Colorpicker | Ctrl + Shift + C | Command + Shift + C
Set black color | Ctrl + Shift + 1 | Command + Shift + 1
Set blue color | Ctrl + Shift + 2 | Command + Shift + 2
Set green color | Ctrl + Shift + 3 | Command + Shift + 3
Set yellow color | Ctrl + Shift + 4 | Command + Shift + 4
Set red color | Ctrl + Shift + 5 | Command + Shift + 5
Save whiteboard as image | Ctrl + S | Command + S
Save whiteboard as JSON | Ctrl + Shift + K | Command + Shift + K
Save whiteboard to WebDav | Ctrl + Shift + I (i) | Command + Shift + I (i)
Load saved JSON to whiteboard | Ctrl + Shift + J | Command + Shift + J
Share whiteboard | Ctrl + Shift + S | Command + Shift + S
Hide or show toolbar | Tab | Tab
Move selected object up | Up Arrow | Up Arrow
Move selected object down | Down Arrow | Down Arrow
Move selected object left | Left Arrow | Left Arrow
Move selected object right | Right Arrow | Right Arrow
Drop object | Ctrl + Enter | Command + Enter
Add Image to backgroud | Shift + Enter | Shift + Enter
Cancel all actions | Escape | Escape
Delete selected object | Delete | Delete
Use Line tool when pen is active (Not changeable) | Shift (Hold) | Shift (Hold)
| Result | Windows and Linux | macOS |
| ---------------------------------------------------------------- | -------------------- | ----------------------- |
| Clear the whiteboard | Ctrl + Shift + Z | Command + Shift + Z |
| Undo your last step | Ctrl + Z | Command + Z |
| Redo your last undo | Ctrl + Y | Command + Y |
| Select an area | Ctrl + X | Command + X |
| Take the mouse | Ctrl + M | Command + M |
| Take the pen | Ctrl + P | Command + P |
| Draw a line | Ctrl + L | Command + L |
| Draw a rectangle | Ctrl + R | Command + R |
| Draw a circle | Ctrl + C | Command + C |
| Toggle between line, rectangle and circle | Ctrl + Shift + F | Command + Shift + F |
| Toggle between pen and eraser | Ctrl + Shift + X | Command + Shift + X |
| Toggle between main clolors (black, blue, green, yellow and red) | Ctrl + Shift + R | Command + Shift + R |
| Write text | Ctrl + A | Command + A |
| Take the eraser | Ctrl + E | Command + E |
| Increase thickness | Ctrl + Up Arrow | Command + Up Arrow |
| Decrease thickness | Ctrl + Down Arrow | Command + Down Arrow |
| Colorpicker | Ctrl + Shift + C | Command + Shift + C |
| Set black color | Ctrl + Shift + 1 | Command + Shift + 1 |
| Set blue color | Ctrl + Shift + 2 | Command + Shift + 2 |
| Set green color | Ctrl + Shift + 3 | Command + Shift + 3 |
| Set yellow color | Ctrl + Shift + 4 | Command + Shift + 4 |
| Set red color | Ctrl + Shift + 5 | Command + Shift + 5 |
| Save whiteboard as image | Ctrl + S | Command + S |
| Save whiteboard as JSON | Ctrl + Shift + K | Command + Shift + K |
| Save whiteboard to WebDav | Ctrl + Shift + I (i) | Command + Shift + I (i) |
| Load saved JSON to whiteboard | Ctrl + Shift + J | Command + Shift + J |
| Share whiteboard | Ctrl + Shift + S | Command + Shift + S |
| Hide or show toolbar | Tab | Tab |
| Move selected object up | Up Arrow | Up Arrow |
| Move selected object down | Down Arrow | Down Arrow |
| Move selected object left | Left Arrow | Left Arrow |
| Move selected object right | Right Arrow | Right Arrow |
| Drop object | Ctrl + Enter | Command + Enter |
| Add Image to backgroud | Shift + Enter | Shift + Enter |
| Cancel all actions | Escape | Escape |
| Delete selected object | Delete | Delete |
| Use Line tool when pen is active (Not changeable) | Shift (Hold) | Shift (Hold) |
## URL Parameters
Call your site with GET parameters to change the WhiteboardID or the Username
`http://YOURIP:8080?whiteboardid=MYID&username=MYNAME`
* whiteboardid => All people with the same ID are drawing on the same board
* username => The name witch is showing to others while drawing
* title => Change the name of the Browser Tab
* randomid => if set to true, a random whiteboardId will be generated if not given aswell
- whiteboardid => All people with the same ID are drawing on the same board
- username => The name witch is showing to others while drawing
- title => Change the name of the Browser Tab
- randomid => if set to true, a random whiteboardId will be generated if not given aswell
## Security - AccessToken (Optional)
To prevent clients who might know or guess the base URL from abusing the server to upload files and stuff..., you can set an accesstoken at server start.
<b>Server (Without docker):</b> `node scripts/server.js --accesstoken="mySecToken"`
@ -107,6 +117,7 @@ Then set the same token on the client side as well:
Done!
## WebDAV (Optional)
This function allows your users to save the whiteboard directly to a webdav server (Nextcloud) as image without downloading it.
To enable it:
@ -125,21 +136,25 @@ Note: For the most owncloud/nextcloud setups you have to set the WebDav-Server U
Done!
## Things you may want to know
* Whiteboards are gone if you restart the Server, so keep that in mind (or save your whiteboard)
* You should be able to customize the layout without ever touching the whiteboard.js (take a look at index.html & main.js)
- Whiteboards are gone if you restart the Server, so keep that in mind (or save your whiteboard)
- You should be able to customize the layout without ever touching the whiteboard.js (take a look at index.html & main.js)
## All server start parameters (also docker)
* accesstoken => take a look at "Security - AccessToken" for a full explanation
* disablesmallestscreen => set this to "true" if you don't want show the "smallest screen" indicator (A dotted gray line) to the users
* webdav => Enable the function to save to a webdav-server (Must also be enabled on the client; Take a look at the webdav section)
- accesstoken => take a look at "Security - AccessToken" for a full explanation
- disablesmallestscreen => set this to "true" if you don't want show the "smallest screen" indicator (A dotted gray line) to the users
- webdav => Enable the function to save to a webdav-server (Must also be enabled on the client; Take a look at the webdav section)
## ToDo
* Make undo function more reliable on texts
- Make undo function more reliable on texts
## Nginx Reverse Proxy configuration
Add this to your server part:
```
location /whiteboard/ {
proxy_set_header HOST $host;
@ -149,22 +164,22 @@ Add this to your server part:
proxy_pass http://YOURIP:8080/;
}
```
To run it at /whiteboard. Don't forget to change -> YOURIP!
## Nextcloud integration
1. Install this app on your server
2. Enable and go to "external sites" (app) on your Nextcloud
2. Add a link to your server: `https://YOURIP/whiteboard/?whiteboardid=WHITEBOARDNAME&username={uid}`
You can give each group its own whiteboard by changeing the WHITEBOARDNAME in the URL if you want.
3. Add a link to your server: `https://YOURIP/whiteboard/?whiteboardid=WHITEBOARDNAME&username={uid}`
You can give each group its own whiteboard by changeing the WHITEBOARDNAME in the URL if you want.
Note: You might have to serve the app with https (If your nextcloud server runs https). To do so, its recommend to run this app behind a reverse proxy. (as shown above)
#### (Optional) Set whiteboard icon in nextcloud
![start](https://raw.githubusercontent.com/cracker0dks/whiteboard/master/doc/iconPrev.jpg)
Upload both icons present at /doc/nextcloud_icons/ to your nextcloud at the "external sites" admin section. Then set it as symbol on your link.
___ MIT License ___
**_ MIT License _**

View File

@ -1,61 +1,59 @@
const webpack = require("webpack");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
const config = {
entry: {
main: ["./src/js/index.js"],
},
output: {
path: path.join(__dirname, "..", "dist"),
filename: "[name]-[hash].js"
},
resolve: {
extensions: ["*", ".json", ".js"]
},
module: {
rules: [
{
test: /\.(js)$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
compact: true
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
},
entry: {
main: ["./src/js/index.js"],
},
output: {
path: path.join(__dirname, "..", "dist"),
filename: "[name]-[hash].js",
},
resolve: {
extensions: ["*", ".json", ".js"],
},
module: {
rules: [
{
test: /\.(js)$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
compact: true,
},
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: "file-loader",
},
],
},
],
}
]
},
plugins: [
new CleanWebpackPlugin(),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
"window.jQuery": "jquery",
"window.$": "jquery",
}),
new CopyPlugin([
{ from: 'assets', to: '' },
]),
new HtmlWebpackPlugin({
template: 'src/index.html',
minify: false,
inject: true
})
]
},
plugins: [
new CleanWebpackPlugin(),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery",
"window.$": "jquery",
}),
new CopyPlugin([{ from: "assets", to: "" }]),
new HtmlWebpackPlugin({
template: "src/index.html",
minify: false,
inject: true,
}),
],
};
module.exports = config;

View File

@ -2,10 +2,10 @@ const merge = require("webpack-merge");
const baseConfig = require("./webpack.base");
module.exports = merge(baseConfig, {
mode: "production",
optimization: {
minimize: true,
nodeEnv: "production",
},
devtool: false
mode: "production",
optimization: {
minimize: true,
nodeEnv: "production",
},
devtool: false,
});

View File

@ -3,16 +3,16 @@ const baseConfig = require("./webpack.base");
const webpack = require("webpack");
const devConfig = merge(baseConfig, {
mode: "development",
devtool: "eval-source-map",
optimization: {
minimize: false,
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
].concat(baseConfig.plugins),
mode: "development",
devtool: "eval-source-map",
optimization: {
minimize: false,
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
].concat(baseConfig.plugins),
});
module.exports = devConfig;

View File

@ -1,4 +1,4 @@
version: '3.1'
version: "3.1"
services:
whiteboard:
image: rofl256/whiteboard

521
package-lock.json generated
View File

@ -1069,6 +1069,12 @@
"integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==",
"dev": true
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true
},
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
@ -1104,6 +1110,12 @@
"integrity": "sha512-WE4IOAC6r/yBZss1oQGM5zs2D7RuKR6Q+w+X2SouPofnWn+LbCqClRyhO3ZE7Ix8nmFgo/oVuuE01cJT2XB13A==",
"dev": true
},
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
},
"@types/source-list-map": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
@ -1469,6 +1481,12 @@
"integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
"dev": true
},
"array-differ": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz",
"integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==",
"dev": true
},
"array-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
@ -1505,6 +1523,12 @@
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
},
"arrify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
"dev": true
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@ -2304,6 +2328,12 @@
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
},
"camel-case": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz",
@ -2377,6 +2407,12 @@
"tslib": "^1.9.0"
}
},
"ci-info": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"dev": true
},
"cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
@ -2491,6 +2527,12 @@
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"compare-versions": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
"integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==",
"dev": true
},
"component-bind": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
@ -2764,6 +2806,27 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"dev": true,
"requires": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
},
"dependencies": {
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
}
}
},
"create-ecdh": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
@ -3365,6 +3428,15 @@
"prr": "~1.0.1"
}
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"requires": {
"is-arrayish": "^0.2.1"
}
},
"es-abstract": {
"version": "1.17.5",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
@ -3774,6 +3846,15 @@
"locate-path": "^2.0.0"
}
},
"find-versions": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz",
"integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==",
"dev": true,
"requires": {
"semver-regex": "^2.0.0"
}
},
"findup-sync": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
@ -4899,6 +4980,140 @@
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
"dev": true
},
"husky": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz",
"integrity": "sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==",
"dev": true,
"requires": {
"chalk": "^4.0.0",
"ci-info": "^2.0.0",
"compare-versions": "^3.6.0",
"cosmiconfig": "^6.0.0",
"find-versions": "^3.2.0",
"opencollective-postinstall": "^2.0.2",
"pkg-dir": "^4.2.0",
"please-upgrade-node": "^3.2.0",
"slash": "^3.0.0",
"which-pm-runs": "^1.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
"dev": true,
"requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
"integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"requires": {
"p-locate": "^4.1.0"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"requires": {
"p-limit": "^2.2.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
"dev": true,
"requires": {
"find-up": "^4.0.0"
}
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
},
"supports-color": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
"integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"iconv-lite": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
@ -4931,6 +5146,24 @@
"integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
"dev": true
},
"import-fresh": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
"integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
"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
}
}
},
"import-local": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
@ -5073,6 +5306,12 @@
"integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
"dev": true
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
"is-binary-path": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
@ -5460,6 +5699,12 @@
"type-check": "~0.3.2"
}
},
"lines-and-columns": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
"dev": true
},
"loader-runner": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
@ -5628,6 +5873,12 @@
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@ -5775,6 +6026,12 @@
"run-queue": "^1.0.3"
}
},
"mri": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.1.5.tgz",
"integrity": "sha512-d2RKzMD4JNyHMbnbWnznPaa8vbdlq/4pNZ3IgdaGrVbBhebBsGUUE/6qorTMYNS6TwuH3ilfOlD2bf4Igh8CKg==",
"dev": true
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -5796,6 +6053,27 @@
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
"dev": true
},
"multimatch": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz",
"integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==",
"dev": true,
"requires": {
"@types/minimatch": "^3.0.3",
"array-differ": "^3.0.0",
"array-union": "^2.1.0",
"arrify": "^2.0.1",
"minimatch": "^3.0.4"
},
"dependencies": {
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true
}
}
},
"nan": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
@ -6078,6 +6356,21 @@
"wrappy": "1"
}
},
"onetime": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
"integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
"dev": true,
"requires": {
"mimic-fn": "^2.1.0"
}
},
"opencollective-postinstall": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz",
"integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==",
"dev": true
},
"opn": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
@ -6210,6 +6503,15 @@
"tslib": "^1.10.0"
}
},
"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"
}
},
"parse-asn1": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
@ -6224,6 +6526,18 @@
"safe-buffer": "^5.1.1"
}
},
"parse-json": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
"integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1",
"lines-and-columns": "^1.1.6"
}
},
"parse-passwd": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
@ -6452,6 +6766,15 @@
"find-up": "^2.1.0"
}
},
"please-upgrade-node": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
"integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==",
"dev": true,
"requires": {
"semver-compare": "^1.0.0"
}
},
"pn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
@ -6576,6 +6899,12 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
},
"prettier": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz",
"integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==",
"dev": true
},
"pretty-error": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz",
@ -6586,6 +6915,165 @@
"utila": "~0.4"
}
},
"pretty-quick": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-2.0.1.tgz",
"integrity": "sha512-y7bJt77XadjUr+P1uKqZxFWLddvj3SKY6EU4BuQtMxmmEFSMpbN132pUWdSG1g1mtUfO0noBvn7wBf0BVeomHg==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"execa": "^2.1.0",
"find-up": "^4.1.0",
"ignore": "^5.1.4",
"mri": "^1.1.4",
"multimatch": "^4.0.0"
},
"dependencies": {
"cross-spawn": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz",
"integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==",
"dev": true,
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
}
},
"execa": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz",
"integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==",
"dev": true,
"requires": {
"cross-spawn": "^7.0.0",
"get-stream": "^5.0.0",
"is-stream": "^2.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^3.0.0",
"onetime": "^5.1.0",
"p-finally": "^2.0.0",
"signal-exit": "^3.0.2",
"strip-final-newline": "^2.0.0"
}
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"get-stream": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
"integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==",
"dev": true,
"requires": {
"pump": "^3.0.0"
}
},
"ignore": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
"integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==",
"dev": true
},
"is-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
"dev": true
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"requires": {
"p-locate": "^4.1.0"
}
},
"npm-run-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz",
"integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==",
"dev": true,
"requires": {
"path-key": "^3.0.0"
}
},
"p-finally": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz",
"integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==",
"dev": true
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"requires": {
"p-limit": "^2.2.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"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
},
"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
},
"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"
}
}
}
},
"private": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
@ -7200,6 +7688,18 @@
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"semver-compare": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
"dev": true
},
"semver-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz",
"integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==",
"dev": true
},
"send": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz",
@ -7833,6 +8333,12 @@
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
"dev": true
},
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"dev": true
},
"style-loader": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.1.4.tgz",
@ -9269,6 +9775,12 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"which-pm-runs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=",
"dev": true
},
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
@ -9377,6 +9889,15 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
"yaml": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz",
"integrity": "sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.9.2"
}
},
"yargs": {
"version": "13.2.4",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz",

View File

@ -8,7 +8,10 @@
"build": "webpack --config config/webpack.build.js",
"start:dev": "node scripts/server.js --mode=development",
"start:prod": "npm run build && node scripts/server.js --mode=production",
"test": "echo \"No tests needed!\" && exit 1"
"test": "echo \"No tests needed!\" && exit 1",
"pretty-quick": "pretty-quick",
"format": "prettier --write .",
"style": "prettier --check ."
},
"repository": {
"type": "git",
@ -19,6 +22,11 @@
"Sketchboard",
"lightweight"
],
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
},
"dependencies": {
"dompurify": "^2.0.7",
"express": "4.*",
@ -45,9 +53,12 @@
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.5.2",
"html-webpack-plugin": "^4.2.0",
"husky": "^4.2.5",
"jquery": "^3.2.1",
"jquery-ui": "^1.12.1",
"keymage": "^1.1.3",
"prettier": "^2.0.5",
"pretty-quick": "^2.0.1",
"style-loader": "^1.1.4",
"vanilla-picker": "^2.10.1",
"webpack": "^4.42.1",

View File

@ -7,10 +7,12 @@ module.exports = {
var tool = content["t"]; //Tool witch is used
var wid = content["wid"]; //whiteboard ID
var username = content["username"];
if (tool === "clear") { //Clear the whiteboard
if (tool === "clear") {
//Clear the whiteboard
delete savedBoards[wid];
delete savedUndos[wid];
} else if (tool === "undo") { //Undo an action
} else if (tool === "undo") {
//Undo an action
if (!savedUndos[wid]) {
savedUndos[wid] = [];
}
@ -19,7 +21,10 @@ module.exports = {
if (savedBoards[wid][i]["username"] == username) {
var drawId = savedBoards[wid][i]["drawId"];
for (var i = savedBoards[wid].length - 1; i >= 0; i--) {
if (savedBoards[wid][i]["drawId"] == drawId && savedBoards[wid][i]["username"] == username) {
if (
savedBoards[wid][i]["drawId"] == drawId &&
savedBoards[wid][i]["username"] == username
) {
savedUndos[wid].push(savedBoards[wid][i]);
savedBoards[wid].splice(i, 1);
}
@ -27,7 +32,7 @@ module.exports = {
break;
}
}
if(savedUndos[wid].length > 1000) {
if (savedUndos[wid].length > 1000) {
savedUndos[wid].splice(0, savedUndos[wid].length - 1000);
}
}
@ -42,7 +47,10 @@ module.exports = {
if (savedUndos[wid][i]["username"] == username) {
var drawId = savedUndos[wid][i]["drawId"];
for (var i = savedUndos[wid].length - 1; i >= 0; i--) {
if (savedUndos[wid][i]["drawId"] == drawId && savedUndos[wid][i]["username"] == username) {
if (
savedUndos[wid][i]["drawId"] == drawId &&
savedUndos[wid][i]["username"] == username
) {
savedBoards[wid].push(savedUndos[wid][i]);
savedUndos[wid].splice(i, 1);
}
@ -50,14 +58,36 @@ module.exports = {
break;
}
}
} else if (["line", "pen", "rect", "circle", "eraser", "addImgBG", "recSelect", "eraseRec", "addTextBox", "setTextboxText", "removeTextbox", "setTextboxPosition", "setTextboxFontSize", "setTextboxFontColor"].includes(tool)) { //Save all this actions
} else if (
[
"line",
"pen",
"rect",
"circle",
"eraser",
"addImgBG",
"recSelect",
"eraseRec",
"addTextBox",
"setTextboxText",
"removeTextbox",
"setTextboxPosition",
"setTextboxFontSize",
"setTextboxFontColor",
].includes(tool)
) {
//Save all this actions
if (!savedBoards[wid]) {
savedBoards[wid] = [];
}
delete content["wid"]; //Delete id from content so we don't store it twice
if (tool === "setTextboxText") {
for (var i = savedBoards[wid].length - 1; i >= 0; i--) { //Remove old textbox tex -> dont store it twice
if (savedBoards[wid][i]["t"] === "setTextboxText" && savedBoards[wid][i]["d"][0] === content["d"][0]) {
for (var i = savedBoards[wid].length - 1; i >= 0; i--) {
//Remove old textbox tex -> dont store it twice
if (
savedBoards[wid][i]["t"] === "setTextboxText" &&
savedBoards[wid][i]["d"][0] === content["d"][0]
) {
savedBoards[wid].splice(i, 1);
}
}
@ -65,7 +95,8 @@ module.exports = {
savedBoards[wid].push(content);
}
},
loadStoredData: function (wid) { //Load saved whiteboard
loadStoredData: function (wid) {
//Load saved whiteboard
return savedBoards[wid] ? savedBoards[wid] : [];
}
}
},
};

View File

@ -5,26 +5,26 @@ function startBackendServer(port) {
var accessToken = ""; //Can be set here or as start parameter (node server.js --accesstoken=MYTOKEN)
var disableSmallestScreen = false; //Can be set to true if you dont want to show (node server.js --disablesmallestscreen=true)
var webdav = false; //Can be set to true if you want to allow webdav save (node server.js --webdav=true)
var fs = require("fs-extra");
var express = require('express');
var formidable = require('formidable'); //form upload processing
const createDOMPurify = require('dompurify'); //Prevent xss
const { JSDOM } = require('jsdom');
const window = (new JSDOM('')).window;
var express = require("express");
var formidable = require("formidable"); //form upload processing
const createDOMPurify = require("dompurify"); //Prevent xss
const { JSDOM } = require("jsdom");
const window = new JSDOM("").window;
const DOMPurify = createDOMPurify(window);
const { createClient } = require("webdav");
var s_whiteboard = require("./s_whiteboard.js");
var app = express();
app.use(express.static(path.join(__dirname, '..', 'dist')));
app.use("/uploads", express.static(path.join(__dirname, '..', 'public', 'uploads')));
var server = require('http').Server(app);
app.use(express.static(path.join(__dirname, "..", "dist")));
app.use("/uploads", express.static(path.join(__dirname, "..", "public", "uploads")));
var server = require("http").Server(app);
server.listen(port);
var io = require('socket.io')(server, {path: "/ws-api", });
var io = require("socket.io")(server, { path: "/ws-api" });
console.log("Webserver & socketserver running on port:" + port);
if (process.env.accesstoken) {
accessToken = process.env.accesstoken;
@ -35,7 +35,7 @@ function startBackendServer(port) {
if (process.env.webdav) {
webdav = true;
}
var startArgs = getArgs();
if (startArgs["accesstoken"]) {
accessToken = startArgs["accesstoken"];
@ -46,7 +46,7 @@ function startBackendServer(port) {
if (startArgs["webdav"]) {
webdav = true;
}
if (accessToken !== "") {
console.log("AccessToken set to: " + accessToken);
}
@ -56,8 +56,8 @@ function startBackendServer(port) {
if (webdav) {
console.log("Webdav save is enabled!");
}
app.get('/api/loadwhiteboard', function (req, res) {
app.get("/api/loadwhiteboard", function (req, res) {
var wid = req["query"]["wid"];
var at = req["query"]["at"]; //accesstoken
if (accessToken === "" || accessToken == at) {
@ -65,31 +65,32 @@ function startBackendServer(port) {
res.send(ret);
res.end();
} else {
res.status(401); //Unauthorized
res.status(401); //Unauthorized
res.end();
}
});
app.post('/api/upload', function (req, res) { //File upload
app.post("/api/upload", function (req, res) {
//File upload
var form = new formidable.IncomingForm(); //Receive form
var formData = {
files: {},
fields: {}
}
form.on('file', function (name, file) {
fields: {},
};
form.on("file", function (name, file) {
formData["files"][file.name] = file;
});
form.on('field', function (name, value) {
form.on("field", function (name, value) {
formData["fields"][name] = value;
});
form.on('error', function (err) {
console.log('File uplaod Error!');
form.on("error", function (err) {
console.log("File uplaod Error!");
});
form.on('end', function () {
form.on("end", function () {
if (accessToken === "" || accessToken == formData["fields"]["at"]) {
progressUploadFormData(formData, function (err) {
if (err) {
@ -104,22 +105,22 @@ function startBackendServer(port) {
}
});
} else {
res.status(401); //Unauthorized
res.status(401); //Unauthorized
res.end();
}
//End file upload
});
form.parse(req);
});
function progressUploadFormData(formData, callback) {
console.log("Progress new Form Data");
var fields = escapeAllContentStrings(formData.fields);
var files = formData.files;
var whiteboardId = fields["whiteboardId"];
var name = fields["name"] || "";
var date = fields["date"] || (+new Date());
var date = fields["date"] || +new Date();
var filename = whiteboardId + "_" + date + ".png";
var webdavaccess = fields["webdavaccess"] || false;
try {
@ -133,24 +134,33 @@ function startBackendServer(port) {
return;
}
var imagedata = fields["imagedata"];
if (imagedata && imagedata != "") { //Save from base64 data
imagedata = imagedata.replace(/^data:image\/png;base64,/, "").replace(/^data:image\/jpeg;base64,/, "");
if (imagedata && imagedata != "") {
//Save from base64 data
imagedata = imagedata
.replace(/^data:image\/png;base64,/, "")
.replace(/^data:image\/jpeg;base64,/, "");
console.log(filename, "uploaded");
fs.writeFile('./public/uploads/' + filename, imagedata, 'base64', function (err) {
fs.writeFile("./public/uploads/" + filename, imagedata, "base64", function (err) {
if (err) {
console.log("error", err);
callback(err);
} else {
if (webdavaccess) { //Save image to webdav
if (webdavaccess) {
//Save image to webdav
if (webdav) {
saveImageToWebdav('./public/uploads/' + filename, filename, webdavaccess, function (err) {
if (err) {
console.log("error", err);
callback(err);
} else {
callback();
saveImageToWebdav(
"./public/uploads/" + filename,
filename,
webdavaccess,
function (err) {
if (err) {
console.log("error", err);
callback(err);
} else {
callback();
}
}
})
);
} else {
callback("Webdav is not enabled on the server!");
}
@ -165,78 +175,92 @@ function startBackendServer(port) {
}
});
}
function saveImageToWebdav(imagepath, filename, webdavaccess, callback) {
if (webdavaccess) {
var webdavserver = webdavaccess["webdavserver"] || "";
var webdavpath = webdavaccess["webdavpath"] || "/";
var webdavusername = webdavaccess["webdavusername"] || "";
var webdavpassword = webdavaccess["webdavpassword"] || "";
const client = createClient(
webdavserver,
{
username: webdavusername,
password: webdavpassword
}
)
client.getDirectoryContents(webdavpath).then((items) => {
var cloudpath = webdavpath+ '' + filename;
console.log("webdav saving to:", cloudpath);
fs.createReadStream(imagepath).pipe(client.createWriteStream(cloudpath));
callback();
}).catch((error) => {
callback("403");
console.log("Could not connect to webdav!")
const client = createClient(webdavserver, {
username: webdavusername,
password: webdavpassword,
});
client
.getDirectoryContents(webdavpath)
.then((items) => {
var cloudpath = webdavpath + "" + filename;
console.log("webdav saving to:", cloudpath);
fs.createReadStream(imagepath).pipe(client.createWriteStream(cloudpath));
callback();
})
.catch((error) => {
callback("403");
console.log("Could not connect to webdav!");
});
} else {
callback("Error: no access data!")
callback("Error: no access data!");
}
}
var smallestScreenResolutions = {};
io.on('connection', function (socket) {
io.on("connection", function (socket) {
var whiteboardId = null;
socket.on('disconnect', function () {
if (smallestScreenResolutions && smallestScreenResolutions[whiteboardId] && socket && socket.id) {
socket.on("disconnect", function () {
if (
smallestScreenResolutions &&
smallestScreenResolutions[whiteboardId] &&
socket &&
socket.id
) {
delete smallestScreenResolutions[whiteboardId][socket.id];
}
socket.compress(false).broadcast.emit('refreshUserBadges', null); //Removes old user Badges
socket.compress(false).broadcast.emit("refreshUserBadges", null); //Removes old user Badges
sendSmallestScreenResolution();
});
socket.on('drawToWhiteboard', function (content) {
socket.on("drawToWhiteboard", function (content) {
content = escapeAllContentStrings(content);
if (accessToken === "" || accessToken == content["at"]) {
socket.compress(false).broadcast.to(whiteboardId).emit('drawToWhiteboard', content); //Send to all users in the room (not own socket)
socket.compress(false).broadcast.to(whiteboardId).emit("drawToWhiteboard", content); //Send to all users in the room (not own socket)
s_whiteboard.handleEventsAndData(content); //save whiteboardchanges on the server
} else {
socket.emit('wrongAccessToken', true);
socket.emit("wrongAccessToken", true);
}
});
socket.on('joinWhiteboard', function (content) {
socket.on("joinWhiteboard", function (content) {
content = escapeAllContentStrings(content);
if (accessToken === "" || accessToken == content["at"]) {
whiteboardId = content["wid"];
socket.join(whiteboardId); //Joins room name=wid
smallestScreenResolutions[whiteboardId] = smallestScreenResolutions[whiteboardId] ? smallestScreenResolutions[whiteboardId] : {};
smallestScreenResolutions[whiteboardId][socket.id] = content["windowWidthHeight"] || { w: 10000, h: 10000 };
smallestScreenResolutions[whiteboardId] = smallestScreenResolutions[whiteboardId]
? smallestScreenResolutions[whiteboardId]
: {};
smallestScreenResolutions[whiteboardId][socket.id] = content[
"windowWidthHeight"
] || { w: 10000, h: 10000 };
sendSmallestScreenResolution();
} else {
socket.emit('wrongAccessToken', true);
socket.emit("wrongAccessToken", true);
}
});
socket.on('updateScreenResolution', function (content) {
socket.on("updateScreenResolution", function (content) {
content = escapeAllContentStrings(content);
if (smallestScreenResolutions[whiteboardId] && (accessToken === "" || accessToken == content["at"])) {
smallestScreenResolutions[whiteboardId][socket.id] = content["windowWidthHeight"] || { w: 10000, h: 10000 };
if (
smallestScreenResolutions[whiteboardId] &&
(accessToken === "" || accessToken == content["at"])
) {
smallestScreenResolutions[whiteboardId][socket.id] = content[
"windowWidthHeight"
] || { w: 10000, h: 10000 };
sendSmallestScreenResolution();
}
});
function sendSmallestScreenResolution() {
if (disableSmallestScreen) {
return;
@ -244,35 +268,44 @@ function startBackendServer(port) {
var smallestWidth = 10000;
var smallestHeight = 10000;
for (var i in smallestScreenResolutions[whiteboardId]) {
smallestWidth = smallestWidth > smallestScreenResolutions[whiteboardId][i]["w"] ? smallestScreenResolutions[whiteboardId][i]["w"] : smallestWidth;
smallestHeight = smallestHeight > smallestScreenResolutions[whiteboardId][i]["h"] ? smallestScreenResolutions[whiteboardId][i]["h"] : smallestHeight;
smallestWidth =
smallestWidth > smallestScreenResolutions[whiteboardId][i]["w"]
? smallestScreenResolutions[whiteboardId][i]["w"]
: smallestWidth;
smallestHeight =
smallestHeight > smallestScreenResolutions[whiteboardId][i]["h"]
? smallestScreenResolutions[whiteboardId][i]["h"]
: smallestHeight;
}
io.to(whiteboardId).emit('updateSmallestScreenResolution', { w: smallestWidth, h: smallestHeight });
io.to(whiteboardId).emit("updateSmallestScreenResolution", {
w: smallestWidth,
h: smallestHeight,
});
}
});
//Prevent cross site scripting (xss)
function escapeAllContentStrings(content, cnt) {
if (!cnt)
cnt = 0;
if (typeof (content) === "string") {
if (!cnt) cnt = 0;
if (typeof content === "string") {
return DOMPurify.sanitize(content);
}
for (var i in content) {
if (typeof (content[i]) === "string") {
if (typeof content[i] === "string") {
content[i] = DOMPurify.sanitize(content[i]);
} if (typeof (content[i]) === "object" && cnt < 10) {
}
if (typeof content[i] === "object" && cnt < 10) {
content[i] = escapeAllContentStrings(content[i], ++cnt);
}
}
return content;
}
process.on('unhandledRejection', error => {
process.on("unhandledRejection", (error) => {
// Will print "unhandledRejection err is not defined"
console.log('unhandledRejection', error.message);
})
console.log("unhandledRejection", error.message);
});
}
module.exports = startBackendServer;
module.exports = startBackendServer;

View File

@ -1,34 +1,33 @@
const devServerConfig = {
hot: true,
inline: true,
stats: {
children: false,
maxModules: 0
},
proxy: {
// proxies for the backend
'/api': 'http://localhost:3000',
'/uploads': 'http://localhost:3000',
'/ws-api': {
target: 'ws://localhost:3000',
ws: true,
}
}
}
hot: true,
inline: true,
stats: {
children: false,
maxModules: 0,
},
proxy: {
// proxies for the backend
"/api": "http://localhost:3000",
"/uploads": "http://localhost:3000",
"/ws-api": {
target: "ws://localhost:3000",
ws: true,
},
},
};
function startFrontendDevServer(port) {
// require here to prevent prod dependency to webpack
const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
const config = require("../config/webpack.dev");
// require here to prevent prod dependency to webpack
const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
const config = require("../config/webpack.dev");
new WebpackDevServer(webpack(config), devServerConfig)
.listen(port, (err) => {
if (err) {
console.log(err);
}
new WebpackDevServer(webpack(config), devServerConfig).listen(port, (err) => {
if (err) {
console.log(err);
}
console.log("Listening on port " + port);
console.log("Listening on port " + port);
});
}

View File

@ -4,23 +4,23 @@ const startBackendServer = require("./server-backend");
const SERVER_MODES = {
PRODUCTION: 1,
DEVELOPMENT: 2
}
DEVELOPMENT: 2,
};
const args = getArgs();
if ( typeof args.mode === "undefined") {
if (typeof args.mode === "undefined") {
// default to production mode
args.mode = "production";
}
if (args.mode !== "production" && args.mode !== "development") {
throw new Error("--mode can only be 'development' or 'production'")
throw new Error("--mode can only be 'development' or 'production'");
}
const server_mode = args.mode === "production" ? SERVER_MODES.PRODUCTION : SERVER_MODES.DEVELOPMENT;
if (server_mode === SERVER_MODES.DEVELOPMENT){
if (server_mode === SERVER_MODES.DEVELOPMENT) {
console.info("Starting server in development mode.");
startFrontendDevServer(8080);
// this time, it's the frontend server that is on port 8080

View File

@ -1,22 +1,20 @@
function getArgs() {
const args = {}
process.argv
.slice(2, process.argv.length)
.forEach(arg => {
// long arg
if (arg.slice(0, 2) === '--') {
const longArg = arg.split('=')
args[longArg[0].slice(2, longArg[0].length)] = longArg[1]
}
// flags
else if (arg[0] === '-') {
const flags = arg.slice(1, arg.length).split('')
flags.forEach(flag => {
args[flag] = true
})
}
})
return args
const args = {};
process.argv.slice(2, process.argv.length).forEach((arg) => {
// long arg
if (arg.slice(0, 2) === "--") {
const longArg = arg.split("=");
args[longArg[0].slice(2, longArg[0].length)] = longArg[1];
}
// flags
else if (arg[0] === "-") {
const flags = arg.slice(1, arg.length).split("");
flags.forEach((flag) => {
args[flag] = true;
});
}
});
return args;
}
module.exports.getArgs = getArgs;
module.exports.getArgs = getArgs;

View File

@ -36,12 +36,11 @@ button::-moz-focus-inner {
.whiteboard-edit-group.group-disabled {
background: repeating-linear-gradient(
45deg,
rgba(255, 166, 0, 0.366) ,
rgba(255, 166, 0, 0.366),
rgba(255, 166, 0, 0.366) 10px,
rgba(255, 166, 0, 0.666) 10px,
rgba(255, 166, 0, 0.666) 20px
);
}
/*
@ -90,19 +89,21 @@ button {
background: transparent;
outline: none;
opacity: 1;
-webkit-transition: opacity .15s ease-in-out;
transition: opacity .15s ease-in-out;
-webkit-transition: opacity 0.15s ease-in-out;
transition: opacity 0.15s ease-in-out;
}
.textBox.active {
border: 1px dashed gray;
}
.textBox>.removeIcon, .textBox>.moveIcon {
.textBox > .removeIcon,
.textBox > .moveIcon {
display: none;
}
.textBox.active>.removeIcon, .textBox.active>.moveIcon {
.textBox.active > .removeIcon,
.textBox.active > .moveIcon {
display: block;
}

View File

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

View File

@ -1,4 +1,4 @@
import {computeDist} from "../utils";
import { computeDist } from "../utils";
class Point {
/**
@ -42,7 +42,8 @@ class Point {
let x = (e.offsetX || e.pageX - $(e.target).offset().left) + epsilon;
let y = (e.offsetY || e.pageY - $(e.target).offset().top) + epsilon;
if (Number.isNaN(x) || Number.isNaN(y) || (x === epsilon && y === epsilon)) { // if it's a touch actually
if (Number.isNaN(x) || Number.isNaN(y) || (x === epsilon && y === epsilon)) {
// if it's a touch actually
if (e.touches && e.touches.length && e.touches.length > 0) {
const touch = e.touches[0];
x = touch.clientX - $("#mouseOverlay").offset().left;
@ -65,7 +66,7 @@ class Point {
/**
* Compute euclidean distance between points
*
*
* @param {Point} otherPoint
* @returns {number}
*/
@ -74,4 +75,4 @@ class Point {
}
}
export default Point;
export default Point;

View File

@ -17,7 +17,7 @@ import {
faSortDown,
faExpandArrowsAlt,
faLock,
faLockOpen
faLockOpen,
} from "@fortawesome/free-solid-svg-icons";
import {
faSquare,
@ -53,4 +53,4 @@ library.add(
faLockOpen
);
dom.i2svg()
dom.i2svg();

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import keymage from "keymage";
import io from 'socket.io-client';
import io from "socket.io-client";
import whiteboard from "./whiteboard";
import keybinds from "./keybinds";
import Picker from "vanilla-picker";
@ -9,13 +9,16 @@ import shortcutFunctions from "./shortcutFunctions";
import ReadOnlyService from "./services/ReadOnlyService";
function main() {
var whiteboardId = getQueryVariable("whiteboardid");
var randomid = getQueryVariable("randomid");
if (randomid && !whiteboardId) { //set random whiteboard on empty whiteboardid
whiteboardId = Array(2).fill(null).map(() => Math.random().toString(36).substr(2)).join('');
if (randomid && !whiteboardId) {
//set random whiteboard on empty whiteboardid
whiteboardId = Array(2)
.fill(null)
.map(() => Math.random().toString(36).substr(2))
.join("");
const urlParams = new URLSearchParams(window.location.search);
urlParams.set('whiteboardid', whiteboardId);
urlParams.set("whiteboardid", whiteboardId);
window.location.search = urlParams;
}
@ -33,40 +36,44 @@ function main() {
document.title = decodeURIComponent(title);
}
var url = document.URL.substr(0, document.URL.lastIndexOf('/'));
var url = document.URL.substr(0, document.URL.lastIndexOf("/"));
var signaling_socket = null;
var urlSplit = url.split("/");
var subdir = "";
for (var i = 3; i < urlSplit.length; i++) {
subdir = subdir + '/' + urlSplit[i];
subdir = subdir + "/" + urlSplit[i];
}
signaling_socket = io("", { "path": subdir + "/ws-api" }); // Connect even if we are in a subdir behind a reverse proxy
signaling_socket = io("", { path: subdir + "/ws-api" }); // Connect even if we are in a subdir behind a reverse proxy
signaling_socket.on('connect', function () {
signaling_socket.on("connect", function () {
console.log("Websocket connected!");
let messageReceivedCount = 0;
signaling_socket.on('drawToWhiteboard', function (content) {
signaling_socket.on("drawToWhiteboard", function (content) {
whiteboard.handleEventsAndData(content, true);
$('#messageReceivedCount')[0].innerText = String(messageReceivedCount++);
$("#messageReceivedCount")[0].innerText = String(messageReceivedCount++);
});
signaling_socket.on('refreshUserBadges', function () {
signaling_socket.on("refreshUserBadges", function () {
whiteboard.refreshUserBadges();
});
signaling_socket.on('wrongAccessToken', function () {
signaling_socket.on("wrongAccessToken", function () {
if (!accessDenied) {
accessDenied = true;
showBasicAlert("Access denied! Wrong accessToken!")
showBasicAlert("Access denied! Wrong accessToken!");
}
});
signaling_socket.on('updateSmallestScreenResolution', function (widthHeight) {
signaling_socket.on("updateSmallestScreenResolution", function (widthHeight) {
whiteboard.updateSmallestScreenResolution(widthHeight["w"], widthHeight["h"]);
});
signaling_socket.emit('joinWhiteboard', { wid: whiteboardId, at: accessToken, windowWidthHeight: { w: $(window).width(), h: $(window).height() } });
signaling_socket.emit("joinWhiteboard", {
wid: whiteboardId,
at: accessToken,
windowWidthHeight: { w: $(window).width(), h: $(window).height() },
});
});
$(document).ready(function () {
@ -78,29 +85,35 @@ function main() {
}
let messageSentCount = 0;
whiteboard.loadWhiteboard("#whiteboardContainer", { //Load the whiteboard
whiteboard.loadWhiteboard("#whiteboardContainer", {
//Load the whiteboard
whiteboardId: whiteboardId,
username: btoa(myUsername),
sendFunction: function (content) {
if (ReadOnlyService.readOnlyActive) return;
//ADD IN LATER THROUGH CONFIG
// if (content.t === 'cursor') {
//ADD IN LATER THROUGH CONFIG
// if (content.t === 'cursor') {
// if (whiteboard.drawFlag) return;
// }
content["at"] = accessToken;
signaling_socket.emit('drawToWhiteboard', content);
$('#messageSentCount')[0].innerText = String(messageSentCount++);
}
signaling_socket.emit("drawToWhiteboard", content);
$("#messageSentCount")[0].innerText = String(messageSentCount++);
},
});
// request whiteboard from server
$.get(subdir + "/api/loadwhiteboard", { wid: whiteboardId, at: accessToken }).done(function (data) {
whiteboard.loadData(data)
});
$.get(subdir + "/api/loadwhiteboard", { wid: whiteboardId, at: accessToken }).done(
function (data) {
whiteboard.loadData(data);
}
);
$(window).resize(function () {
signaling_socket.emit('updateScreenResolution', { at: accessToken, windowWidthHeight: { w: $(window).width(), h: $(window).height() } });
})
signaling_socket.emit("updateScreenResolution", {
at: accessToken,
windowWidthHeight: { w: $(window).width(), h: $(window).height() },
});
});
/*----------------/
Whiteboard actions
@ -115,12 +128,18 @@ function main() {
tempLineTool = true;
whiteboard.ownCursor.hide();
if (whiteboard.drawFlag) {
whiteboard.mouseup({ offsetX: whiteboard.prevPos.x, offsetY: whiteboard.prevPos.y })
whiteboard.mouseup({
offsetX: whiteboard.prevPos.x,
offsetY: whiteboard.prevPos.y,
});
shortcutFunctions.setTool_line();
whiteboard.mousedown({ offsetX: whiteboard.prevPos.x, offsetY: whiteboard.prevPos.y })
whiteboard.mousedown({
offsetX: whiteboard.prevPos.x,
offsetY: whiteboard.prevPos.y,
});
} else {
shortcutFunctions.setTool_line();
}
}
}
whiteboard.pressedKeys["shift"] = true; //Used for straight lines...
} else if (e.which == 17) {
@ -147,9 +166,15 @@ function main() {
if (associatedShortcutFunction) {
keymage(key, associatedShortcutFunction, { preventDefault: true });
} else {
console.error("Function you want to keybind on key:", key, "named:", functionName, "is not available!")
console.error(
"Function you want to keybind on key:",
key,
"named:",
functionName,
"is not available!"
);
}
})
});
// whiteboard clear button
$("#whiteboardTrashBtn").click(function () {
@ -211,15 +236,18 @@ function main() {
$("#saveAsImageBtn").click(function () {
var imgData = whiteboard.getImageDataBase64();
var w = window.open('about:blank'); //Firefox will not allow downloads without extra window
setTimeout(function () { //FireFox seems to require a setTimeout for this to work.
var a = document.createElement('a');
var w = window.open("about:blank"); //Firefox will not allow downloads without extra window
setTimeout(function () {
//FireFox seems to require a setTimeout for this to work.
var a = document.createElement("a");
a.href = imgData;
a.download = 'whiteboard.png';
a.download = "whiteboard.png";
w.document.body.appendChild(a);
a.click();
w.document.body.removeChild(a);
setTimeout(function () { w.close(); }, 100);
setTimeout(function () {
w.close();
}, 100);
}, 0);
});
@ -227,15 +255,18 @@ function main() {
$("#saveAsJSONBtn").click(function () {
var imgData = whiteboard.getImageDataJson();
var w = window.open('about:blank'); //Firefox will not allow downloads without extra window
setTimeout(function () { //FireFox seems to require a setTimeout for this to work.
var a = document.createElement('a');
a.href = window.URL.createObjectURL(new Blob([imgData], { type: 'text/json' }));
a.download = 'whiteboard.json';
var w = window.open("about:blank"); //Firefox will not allow downloads without extra window
setTimeout(function () {
//FireFox seems to require a setTimeout for this to work.
var a = document.createElement("a");
a.href = window.URL.createObjectURL(new Blob([imgData], { type: "text/json" }));
a.download = "whiteboard.json";
w.document.body.appendChild(a);
a.click();
w.document.body.removeChild(a);
setTimeout(function () { w.close(); }, 100);
setTimeout(function () {
w.close();
}, 100);
}, 0);
});
@ -244,57 +275,67 @@ function main() {
return;
}
var webdavserver = localStorage.getItem('webdavserver') || ""
var webdavpath = localStorage.getItem('webdavpath') || "/"
var webdavusername = localStorage.getItem('webdavusername') || ""
var webdavpassword = localStorage.getItem('webdavpassword') || ""
var webDavHtml = $('<div>' +
'<table>' +
'<tr>' +
'<td>Server URL:</td>' +
'<td><input class="webdavserver" type="text" value="' + webdavserver + '" placeholder="https://yourserver.com/remote.php/webdav/"></td>' +
'<td></td>' +
'</tr>' +
'<tr>' +
'<td>Path:</td>' +
'<td><input class="webdavpath" type="text" placeholder="folder" value="' + webdavpath + '"></td>' +
'<td style="font-size: 0.7em;"><i>path always have to start & end with "/"</i></td>' +
'</tr>' +
'<tr>' +
'<td>Username:</td>' +
'<td><input class="webdavusername" type="text" value="' + webdavusername + '" placeholder="username"></td>' +
'<td style="font-size: 0.7em;"></td>' +
'</tr>' +
'<tr>' +
'<td>Password:</td>' +
'<td><input class="webdavpassword" type="password" value="' + webdavpassword + '" placeholder="password"></td>' +
'<td style="font-size: 0.7em;"></td>' +
'</tr>' +
'<tr>' +
'<td style="font-size: 0.7em;" colspan="3">Note: You have to generate and use app credentials if you have 2 Factor Auth activated on your dav/nextcloud server!</td>' +
'</tr>' +
'<tr>' +
'<td></td>' +
'<td colspan="2"><span class="loadingWebdavText" style="display:none;">Saving to webdav, please wait...</span><button class="modalBtn webdavUploadBtn"><i class="fas fa-upload"></i> Start Upload</button></td>' +
'</tr>' +
'</table>' +
'</div>');
var webdavserver = localStorage.getItem("webdavserver") || "";
var webdavpath = localStorage.getItem("webdavpath") || "/";
var webdavusername = localStorage.getItem("webdavusername") || "";
var webdavpassword = localStorage.getItem("webdavpassword") || "";
var webDavHtml = $(
"<div>" +
"<table>" +
"<tr>" +
"<td>Server URL:</td>" +
'<td><input class="webdavserver" type="text" value="' +
webdavserver +
'" placeholder="https://yourserver.com/remote.php/webdav/"></td>' +
"<td></td>" +
"</tr>" +
"<tr>" +
"<td>Path:</td>" +
'<td><input class="webdavpath" type="text" placeholder="folder" value="' +
webdavpath +
'"></td>' +
'<td style="font-size: 0.7em;"><i>path always have to start & end with "/"</i></td>' +
"</tr>" +
"<tr>" +
"<td>Username:</td>" +
'<td><input class="webdavusername" type="text" value="' +
webdavusername +
'" placeholder="username"></td>' +
'<td style="font-size: 0.7em;"></td>' +
"</tr>" +
"<tr>" +
"<td>Password:</td>" +
'<td><input class="webdavpassword" type="password" value="' +
webdavpassword +
'" placeholder="password"></td>' +
'<td style="font-size: 0.7em;"></td>' +
"</tr>" +
"<tr>" +
'<td style="font-size: 0.7em;" colspan="3">Note: You have to generate and use app credentials if you have 2 Factor Auth activated on your dav/nextcloud server!</td>' +
"</tr>" +
"<tr>" +
"<td></td>" +
'<td colspan="2"><span class="loadingWebdavText" style="display:none;">Saving to webdav, please wait...</span><button class="modalBtn webdavUploadBtn"><i class="fas fa-upload"></i> Start Upload</button></td>' +
"</tr>" +
"</table>" +
"</div>"
);
webDavHtml.find(".webdavUploadBtn").click(function () {
var webdavserver = webDavHtml.find(".webdavserver").val();
localStorage.setItem('webdavserver', webdavserver);
localStorage.setItem("webdavserver", webdavserver);
var webdavpath = webDavHtml.find(".webdavpath").val();
localStorage.setItem('webdavpath', webdavpath);
localStorage.setItem("webdavpath", webdavpath);
var webdavusername = webDavHtml.find(".webdavusername").val();
localStorage.setItem('webdavusername', webdavusername);
localStorage.setItem("webdavusername", webdavusername);
var webdavpassword = webDavHtml.find(".webdavpassword").val();
localStorage.setItem('webdavpassword', webdavpassword);
localStorage.setItem("webdavpassword", webdavpassword);
var base64data = whiteboard.getImageDataBase64();
var webdavaccess = {
webdavserver: webdavserver,
webdavpath: webdavpath,
webdavusername: webdavusername,
webdavpassword: webdavpassword
}
webdavpassword: webdavpassword,
};
webDavHtml.find(".loadingWebdavText").show();
webDavHtml.find(".webdavUploadBtn").hide();
saveWhiteboardToWebdav(base64data, webdavaccess, function (err) {
@ -305,12 +346,12 @@ function main() {
webDavHtml.parents(".basicalert").remove();
}
});
})
});
showBasicAlert(webDavHtml, {
header: "Save to Webdav",
okBtnText: "cancel",
headercolor: "#0082c9"
})
headercolor: "#0082c9",
});
// render newly added icons
dom.i2svg();
});
@ -330,10 +371,15 @@ function main() {
endSplit = endSplit.splice(1, 1);
urlStart += "&" + endSplit.join("&");
}
$("<textarea/>").appendTo("body").val(urlStart).select().each(function () {
document.execCommand('copy');
}).remove();
showBasicAlert("Copied Whiteboard-URL to clipboard.", { hideAfter: 2 })
$("<textarea/>")
.appendTo("body")
.val(urlStart)
.select()
.each(function () {
document.execCommand("copy");
})
.remove();
showBasicAlert("Copied Whiteboard-URL to clipboard.", { hideAfter: 2 });
});
var btnsMini = false;
@ -348,7 +394,7 @@ function main() {
$(this).find("#maxBtn").hide();
}
btnsMini = !btnsMini;
})
});
// load json to whiteboard
$("#myFile").on("change", function () {
@ -374,7 +420,7 @@ function main() {
// handle drag&drop
var dragCounter = 0;
$('#whiteboardContainer').on("dragenter", function (e) {
$("#whiteboardContainer").on("dragenter", function (e) {
if (ReadOnlyService.readOnlyActive) return;
e.preventDefault();
e.stopPropagation();
@ -382,7 +428,7 @@ function main() {
whiteboard.dropIndicator.show();
});
$('#whiteboardContainer').on("dragleave", function (e) {
$("#whiteboardContainer").on("dragleave", function (e) {
if (ReadOnlyService.readOnlyActive) return;
e.preventDefault();
@ -393,11 +439,13 @@ function main() {
}
});
$('#whiteboardContainer').on('drop', function (e) { //Handle drop
$("#whiteboardContainer").on("drop", function (e) {
//Handle drop
if (ReadOnlyService.readOnlyActive) return;
if (e.originalEvent.dataTransfer) {
if (e.originalEvent.dataTransfer.files.length) { //File from harddisc
if (e.originalEvent.dataTransfer.files.length) {
//File from harddisc
e.preventDefault();
e.stopPropagation();
var filename = e.originalEvent.dataTransfer.files[0]["name"];
@ -408,8 +456,9 @@ function main() {
reader.onloadend = function () {
const base64data = reader.result;
uploadImgAndAddToWhiteboard(base64data);
}
} else if (isPDFFileName(filename)) { //Handle PDF Files
};
} else if (isPDFFileName(filename)) {
//Handle PDF Files
var blob = e.originalEvent.dataTransfer.files[0];
var reader = new window.FileReader();
@ -417,86 +466,91 @@ function main() {
var pdfData = new Uint8Array(this.result);
var loadingTask = pdfjsLib.getDocument({ data: pdfData });
loadingTask.promise.then(function (pdf) {
console.log('PDF loaded');
loadingTask.promise.then(
function (pdf) {
console.log("PDF loaded");
var currentDataUrl = null;
var modalDiv = $('<div>' +
'Page: <select></select> ' +
'<button style="margin-bottom: 3px;" class="modalBtn"><i class="fas fa-upload"></i> Upload to Whiteboard</button>' +
'<img style="width:100%;" src=""/>' +
'</div>')
var currentDataUrl = null;
var modalDiv = $(
"<div>" +
"Page: <select></select> " +
'<button style="margin-bottom: 3px;" class="modalBtn"><i class="fas fa-upload"></i> Upload to Whiteboard</button>' +
'<img style="width:100%;" src=""/>' +
"</div>"
);
modalDiv.find("select").change(function () {
showPDFPageAsImage(parseInt($(this).val()));
})
modalDiv.find("button").click(function () {
if (currentDataUrl) {
$(".basicalert").remove();
uploadImgAndAddToWhiteboard(currentDataUrl);
}
})
for (var i = 1; i < pdf.numPages + 1; i++) {
modalDiv.find("select").append('<option value="' + i + '">' + i + '</option>')
}
showBasicAlert(modalDiv, {
header: "Pdf to Image",
okBtnText: "cancel",
headercolor: "#0082c9"
})
showPDFPageAsImage(1);
function showPDFPageAsImage(pageNumber) {
// Fetch the page
pdf.getPage(pageNumber).then(function (page) {
console.log('Page loaded');
var scale = 1.5;
var viewport = page.getViewport({ scale: scale });
// Prepare canvas using PDF page dimensions
var canvas = $("<canvas></canvas>")[0];
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
var renderContext = {
canvasContext: context,
viewport: viewport
};
var renderTask = page.render(renderContext);
renderTask.promise.then(function () {
var dataUrl = canvas.toDataURL("image/jpeg", 1.0);
currentDataUrl = dataUrl;
modalDiv.find("img").attr("src", dataUrl);
console.log('Page rendered');
});
modalDiv.find("select").change(function () {
showPDFPageAsImage(parseInt($(this).val()));
});
modalDiv.find("button").click(function () {
if (currentDataUrl) {
$(".basicalert").remove();
uploadImgAndAddToWhiteboard(currentDataUrl);
}
});
for (var i = 1; i < pdf.numPages + 1; i++) {
modalDiv
.find("select")
.append('<option value="' + i + '">' + i + "</option>");
}
showBasicAlert(modalDiv, {
header: "Pdf to Image",
okBtnText: "cancel",
headercolor: "#0082c9",
});
showPDFPageAsImage(1);
function showPDFPageAsImage(pageNumber) {
// Fetch the page
pdf.getPage(pageNumber).then(function (page) {
console.log("Page loaded");
var scale = 1.5;
var viewport = page.getViewport({ scale: scale });
// Prepare canvas using PDF page dimensions
var canvas = $("<canvas></canvas>")[0];
var context = canvas.getContext("2d");
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
var renderContext = {
canvasContext: context,
viewport: viewport,
};
var renderTask = page.render(renderContext);
renderTask.promise.then(function () {
var dataUrl = canvas.toDataURL("image/jpeg", 1.0);
currentDataUrl = dataUrl;
modalDiv.find("img").attr("src", dataUrl);
console.log("Page rendered");
});
});
}
},
function (reason) {
// PDF loading error
showBasicAlert(
"Error loading pdf as image! Check that this is a vaild pdf file!"
);
console.error(reason);
}
}, function (reason) {
// PDF loading error
showBasicAlert("Error loading pdf as image! Check that this is a vaild pdf file!");
console.error(reason);
});
}
);
};
reader.readAsArrayBuffer(blob);
} else {
showBasicAlert("File must be an image!");
}
} else { //File from other browser
} else {
//File from other browser
var fileUrl = e.originalEvent.dataTransfer.getData('URL');
var imageUrl = e.originalEvent.dataTransfer.getData('text/html');
var fileUrl = e.originalEvent.dataTransfer.getData("URL");
var imageUrl = e.originalEvent.dataTransfer.getData("text/html");
var rex = /src="?([^"\s]+)"?\s*/;
var url = rex.exec(imageUrl);
if (url && url.length > 1) {
@ -529,11 +583,11 @@ function main() {
});
new Picker({
parent: $('#whiteboardColorpicker')[0],
parent: $("#whiteboardColorpicker")[0],
color: "#000000",
onChange: function (color) {
whiteboard.setDrawColor(color.rgbaString);
}
},
});
// on startup select mouse
@ -549,64 +603,76 @@ function main() {
});
//Prevent site from changing tab on drag&drop
window.addEventListener("dragover", function (e) {
e = e || event;
e.preventDefault();
}, false);
window.addEventListener("drop", function (e) {
e = e || event;
e.preventDefault();
}, false);
window.addEventListener(
"dragover",
function (e) {
e = e || event;
e.preventDefault();
},
false
);
window.addEventListener(
"drop",
function (e) {
e = e || event;
e.preventDefault();
},
false
);
function uploadImgAndAddToWhiteboard(base64data) {
var date = (+new Date());
var date = +new Date();
$.ajax({
type: 'POST',
url: document.URL.substr(0, document.URL.lastIndexOf('/')) + '/api/upload',
type: "POST",
url: document.URL.substr(0, document.URL.lastIndexOf("/")) + "/api/upload",
data: {
'imagedata': base64data,
'whiteboardId': whiteboardId,
'date': date,
'at': accessToken
imagedata: base64data,
whiteboardId: whiteboardId,
date: date,
at: accessToken,
},
success: function (msg) {
var filename = whiteboardId + "_" + date + ".png";
whiteboard.addImgToCanvasByUrl(document.URL.substr(0, document.URL.lastIndexOf('/')) + "/uploads/" + filename); //Add image to canvas
whiteboard.addImgToCanvasByUrl(
document.URL.substr(0, document.URL.lastIndexOf("/")) + "/uploads/" + filename
); //Add image to canvas
console.log("Image uploaded!");
},
error: function (err) {
showBasicAlert("Failed to upload frame: " + JSON.stringify(err));
}
},
});
}
function saveWhiteboardToWebdav(base64data, webdavaccess, callback) {
var date = (+new Date());
var date = +new Date();
$.ajax({
type: 'POST',
url: document.URL.substr(0, document.URL.lastIndexOf('/')) + 'api/upload',
type: "POST",
url: document.URL.substr(0, document.URL.lastIndexOf("/")) + "api/upload",
data: {
'imagedata': base64data,
'whiteboardId': whiteboardId,
'date': date,
'at': accessToken,
'webdavaccess': JSON.stringify(webdavaccess)
imagedata: base64data,
whiteboardId: whiteboardId,
date: date,
at: accessToken,
webdavaccess: JSON.stringify(webdavaccess),
},
success: function (msg) {
showBasicAlert("Whiteboard was saved to Webdav!", {
headercolor: "#5c9e5c"
headercolor: "#5c9e5c",
});
console.log("Image uploaded for webdav!");
callback();
},
error: function (err) {
if (err.status == 403) {
showBasicAlert("Could not connect to Webdav folder! Please check the credentials and paths and try again!");
showBasicAlert(
"Could not connect to Webdav folder! Please check the credentials and paths and try again!"
);
} else {
showBasicAlert("Unknown Webdav error! ", err);
}
callback(err);
}
},
});
}
@ -664,13 +730,15 @@ function main() {
console.log("Uploading image!");
let base64data = reader.result;
uploadImgAndAddToWhiteboard(base64data);
}
};
}
}
}
if (!imgItemFound && whiteboard.tool != "text") {
showBasicAlert("Please Drag&Drop the image or pdf into the Whiteboard. (Browsers don't allow copy+past from the filesystem directly)");
showBasicAlert(
"Please Drag&Drop the image or pdf into the Whiteboard. (Browsers don't allow copy+past from the filesystem directly)"
);
}
}
});
@ -681,21 +749,28 @@ function main() {
okBtnText: "Ok",
headercolor: "#d25d5d",
hideAfter: false,
onOkClick: false
}
onOkClick: false,
};
if (newOptions) {
for (var i in newOptions) {
options[i] = newOptions[i];
}
}
var alertHtml = $('<div class="basicalert" style="position:absolute; left:0px; width:100%; top:70px; font-family: monospace;">' +
'<div style="width: 30%; margin: auto; background: #aaaaaa; border-radius: 5px; font-size: 1.2em; border: 1px solid gray;">' +
'<div style="border-bottom: 1px solid #676767; background: ' + options["headercolor"] + '; padding-left: 5px; font-size: 0.8em;">' + options["header"] +
'<div style="float: right; margin-right: 4px; color: #373737; cursor: pointer;" class="closeAlert">x</div></div>' +
'<div style="padding: 10px;" class="htmlcontent"></div>' +
'<div style="height: 20px; padding: 10px;"><button class="modalBtn okbtn" style="float: right;">' + options["okBtnText"] + '</button></div>' +
'</div>' +
'</div>');
var alertHtml = $(
'<div class="basicalert" style="position:absolute; left:0px; width:100%; top:70px; font-family: monospace;">' +
'<div style="width: 30%; margin: auto; background: #aaaaaa; border-radius: 5px; font-size: 1.2em; border: 1px solid gray;">' +
'<div style="border-bottom: 1px solid #676767; background: ' +
options["headercolor"] +
'; padding-left: 5px; font-size: 0.8em;">' +
options["header"] +
'<div style="float: right; margin-right: 4px; color: #373737; cursor: pointer;" class="closeAlert">x</div></div>' +
'<div style="padding: 10px;" class="htmlcontent"></div>' +
'<div style="height: 20px; padding: 10px;"><button class="modalBtn okbtn" style="float: right;">' +
options["okBtnText"] +
"</button></div>" +
"</div>" +
"</div>"
);
alertHtml.find(".htmlcontent").append(html);
$("body").append(alertHtml);
alertHtml.find(".okbtn").click(function () {
@ -703,15 +778,15 @@ function main() {
options.onOkClick();
}
alertHtml.remove();
})
});
alertHtml.find(".closeAlert").click(function () {
alertHtml.remove();
})
});
if (options.hideAfter) {
setTimeout(function () {
alertHtml.find(".okbtn").click();
}, 1000 * options.hideAfter)
}, 1000 * options.hideAfter);
}
}
@ -729,4 +804,4 @@ function main() {
}
}
export default main;
export default main;

View File

@ -57,4 +57,4 @@ class ReadOnlyService {
}
}
export default new ReadOnlyService();
export default new ReadOnlyService();

View File

@ -9,7 +9,7 @@ function defineShortcut(callback, readOnlySensitive = true) {
return () => {
if (readOnlySensitive && ReadOnlyService.readOnlyActive) return;
callback();
}
};
}
const shortcutFunctions = {
@ -103,33 +103,45 @@ const shortcutFunctions = {
}
}),
moveDraggableUp: defineShortcut(() => {
const elm = whiteboard.tool === "text" ? $("#" + whiteboard.latestActiveTextBoxId) : $(".dragMe")[0];
const elm =
whiteboard.tool === "text"
? $("#" + whiteboard.latestActiveTextBoxId)
: $(".dragMe")[0];
const p = $(elm).position();
if (p) $(elm).css({top: p.top - 5, left: p.left})
if (p) $(elm).css({ top: p.top - 5, left: p.left });
}),
moveDraggableDown: defineShortcut(() => {
const elm = whiteboard.tool === "text" ? $("#" + whiteboard.latestActiveTextBoxId) : $(".dragMe")[0];
const elm =
whiteboard.tool === "text"
? $("#" + whiteboard.latestActiveTextBoxId)
: $(".dragMe")[0];
const p = $(elm).position();
if (p) $(elm).css({top: p.top + 5, left: p.left})
if (p) $(elm).css({ top: p.top + 5, left: p.left });
}),
moveDraggableLeft: defineShortcut(() => {
const elm = whiteboard.tool === "text" ? $("#" + whiteboard.latestActiveTextBoxId) : $(".dragMe")[0];
const elm =
whiteboard.tool === "text"
? $("#" + whiteboard.latestActiveTextBoxId)
: $(".dragMe")[0];
const p = $(elm).position();
if (p) $(elm).css({top: p.top, left: p.left - 5})
if (p) $(elm).css({ top: p.top, left: p.left - 5 });
}),
moveDraggableRight: defineShortcut(() => {
const elm = whiteboard.tool === "text" ? $("#" + whiteboard.latestActiveTextBoxId) : $(".dragMe")[0];
const elm =
whiteboard.tool === "text"
? $("#" + whiteboard.latestActiveTextBoxId)
: $(".dragMe")[0];
const p = $(elm).position();
if (p) $(elm).css({top: p.top, left: p.left + 5})
if (p) $(elm).css({ top: p.top, left: p.left + 5 });
}),
dropDraggable: defineShortcut(() => {
$($(".dragMe")[0]).find('.addToCanvasBtn').click();
$($(".dragMe")[0]).find(".addToCanvasBtn").click();
}),
addToBackground: defineShortcut(() => {
$($(".dragMe")[0]).find('.addToBackgroundBtn').click();
$($(".dragMe")[0]).find(".addToBackgroundBtn").click();
}),
cancelAllActions: defineShortcut(() => whiteboard.escKeyAction()),
deleteSelection: defineShortcut(() => whiteboard.delKeyAction()),
}
};
export default shortcutFunctions;

View File

@ -4,9 +4,7 @@
* @param {Point} p2
*/
export function computeDist(p1, p2) {
return Math.sqrt(
Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)
);
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
/**
@ -14,5 +12,5 @@ export function computeDist(p1, p2) {
* @returns {number}
*/
export function getCurrentTimeMs() {
return (new Date()).getTime();
}
return new Date().getTime();
}

File diff suppressed because it is too large Load Diff