diff --git a/package-lock.json b/package-lock.json index 1e8782e28..bcec3222c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,18 @@ "version": "7.11.6", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", "dev": true, "requires": { "@babel/types": "^7.11.5", @@ -790,6 +802,23 @@ "dev": true } } + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true }, "@jest/test-result": { "version": "26.5.2", @@ -1145,6 +1174,120 @@ "css.escape": "^1.5.1", "lodash": "^4.17.15", "redent": "^3.0.0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "cacache": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", + "dev": true, + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "camel-case": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz", + "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==", + "dev": true, + "requires": { + "pascal-case": "^3.1.1", + "tslib": "^1.10.0" + } + }, + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" }, "dependencies": { "ansi-styles": { @@ -1174,6 +1317,11 @@ "requires": { "color-name": "~1.1.4" } + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "color-name": { "version": "1.1.4", @@ -1503,6 +1651,127 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz", + "integrity": "sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^2.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.3", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.1", + "semver": "^7.3.2" + }, + "dependencies": { + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "csstype": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz", + "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==", "dev": true }, "@types/tapable": { @@ -2459,6 +2728,29 @@ "version": "0.2.6", "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, "requires": { "fast-json-stable-stringify": "2.x" @@ -2468,6 +2760,18 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", "dev": true, "requires": { "node-int64": "^0.4.0" @@ -2556,6 +2860,19 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", "dev": true }, "camel-case": { @@ -2604,6 +2921,16 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "dev": true }, "chokidar": { @@ -6766,6 +7093,10 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "klona": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", + "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==", "dev": true }, "loader-runner": { @@ -7004,6 +7335,15 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, + "mini-create-react-context": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", + "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", + "requires": { + "@babel/runtime": "^7.5.5", + "tiny-warning": "^1.0.3" + } + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -7657,6 +7997,20 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + } + } }, "pbkdf2": { "version": "3.1.1", @@ -7722,6 +8076,91 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "dev": true, + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "postcss-selector-parser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true }, "pretty-error": { @@ -7973,6 +8412,37 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "react-router-dom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, "react-simplemde-editor": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/react-simplemde-editor/-/react-simplemde-editor-4.1.3.tgz", @@ -8041,7 +8511,6 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", "dev": true, - "optional": true, "requires": { "picomatch": "^2.2.1" } @@ -8061,6 +8530,10 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" }, "regex-not": { "version": "1.0.2", @@ -8246,6 +8719,11 @@ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -8359,6 +8837,57 @@ "dev": true, "requires": { "remove-trailing-separator": "^1.0.1" + "sass": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.27.0.tgz", + "integrity": "sha512-0gcrER56OkzotK/GGwgg4fPrKuiFlPNitO7eUJ18Bs+/NBlofJfMxmxqpqJxjae9vu0Wq8TZzrSyxZal00WDig==", + "dev": true, + "requires": { + "chokidar": ">=2.0.0 <4.0.0" + } + }, + "sass-loader": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.0.2.tgz", + "integrity": "sha512-wV6NDUVB8/iEYMalV/+139+vl2LaRFlZGEd5/xmdcdzQcgmis+npyco6NsDTVOlNA3y2NV9Gcz+vHyFMIT+ffg==", + "dev": true, + "requires": { + "klona": "^2.0.3", + "loader-utils": "^2.0.0", + "neo-async": "^2.6.2", + "schema-utils": "^2.7.1", + "semver": "^7.3.2" + }, + "dependencies": { + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } } } @@ -8403,6 +8932,9 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true }, "serialize-javascript": { @@ -8944,6 +9476,47 @@ "dev": true, "requires": { "min-indent": "^1.0.0" + "style-loader": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz", + "integrity": "sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.7.0" + }, + "dependencies": { + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + } } }, "supports-color": { @@ -9131,6 +9704,15 @@ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", "dev": true + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, "to-arraybuffer": { "version": "1.0.1", @@ -9404,6 +9986,12 @@ "set-value": "^2.0.1" } }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, "unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -9591,6 +10179,10 @@ "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" }, "vm-browserify": { "version": "1.1.2", diff --git a/package.json b/package.json index b6a4f7d64..c54b0e8e2 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "marked": "^1.1.1", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-router-dom": "^5.2.0", "react-simplemde-editor": "^4.1.3" }, "jest": { @@ -28,9 +29,13 @@ "@types/react": "^16.9.49", "@types/react-dom": "^16.9.8", "copy-webpack-plugin": "^6.0.3", + "css-loader": "^4.3.0", "file-loader": "^6.1.0", "html-webpack-plugin": "^4.5.0", "jest": "^26.5.3", + "sass": "^1.27.0", + "sass-loader": "^10.0.2", + "style-loader": "^1.3.0", "terser-webpack-plugin": "^4.1.0", "ts-jest": "^26.4.1", "ts-loader": "^8.0.3", diff --git a/server/main/main.go b/server/main/main.go index f49eb7156..092ce2a74 100644 --- a/server/main/main.go +++ b/server/main/main.go @@ -347,7 +347,9 @@ func main() { // Static files handleDefault(r, "/") - handleStaticFile(r, "/board", "board.html", "text/html; charset=utf-8") + handleStaticFile(r, "/login", "index.html", "text/html; charset=utf-8") + handleStaticFile(r, "/board", "index.html", "text/html; charset=utf-8") + handleStaticFile(r, "/main.js", "main.js", "text/javascript; charset=utf-8") handleStaticFile(r, "/boardPage.js", "boardPage.js", "text/javascript; charset=utf-8") handleStaticFile(r, "/favicon.ico", "static/favicon.svg", "image/svg+xml; charset=utf-8") diff --git a/src/client/app.tsx b/src/client/app.tsx new file mode 100644 index 000000000..d7bdbc5b0 --- /dev/null +++ b/src/client/app.tsx @@ -0,0 +1,37 @@ +import React from "react" + +import { + BrowserRouter as Router, + Switch, + Route, + Link +} from "react-router-dom" + +import LoginPage from './pages/loginPage' +import BoardPage from './pages/boardPage' + +export default function App() { + return ( + +
+
+ OCTO +
+ +
+ + + + + + + + +
+ +
+
+
+
+ ) +} diff --git a/src/client/boardPage.tsx b/src/client/boardPage.tsx deleted file mode 100644 index bc9009a62..000000000 --- a/src/client/boardPage.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import React from "react" -import ReactDOM from "react-dom" -import { BoardTree } from "./boardTree" -import { BoardView } from "./boardView" -import { CardTree } from "./cardTree" -import { CardDialog } from "./components/cardDialog" -import { FilterComponent } from "./components/filterComponent" -import { PageHeader } from "./components/pageHeader" -import { WorkspaceComponent } from "./components/workspaceComponent" -import { FlashMessage } from "./flashMessage" -import { Mutator } from "./mutator" -import { OctoClient } from "./octoClient" -import { OctoListener } from "./octoListener" -import { IBlock, IPageController } from "./octoTypes" -import { UndoManager } from "./undomanager" -import { Utils } from "./utils" -import { WorkspaceTree } from "./workspaceTree" - -class BoardPage implements IPageController { - boardTitle: HTMLElement - mainBoardHeader: HTMLElement - mainBoardBody: HTMLElement - groupByButton: HTMLElement - groupByLabel: HTMLElement - - boardId?: string - viewId?: string - - workspaceTree: WorkspaceTree - boardTree?: BoardTree - view: BoardView - - updateTitleTimeout: number - updatePropertyLabelTimeout: number - - shownCardTree: CardTree - - private filterAnchorElement?: HTMLElement - private octo = new OctoClient() - private boardListener = new OctoListener() - private cardListener = new OctoListener() - - constructor() { - const queryString = new URLSearchParams(window.location.search) - const boardId = queryString.get("id") - const viewId = queryString.get("v") - - this.layoutPage() - - this.workspaceTree = new WorkspaceTree(this.octo) - - console.log(`BoardPage. boardId: ${this.boardId}`) - if (boardId) { - this.attachToBoard(boardId, viewId) - } else { - this.sync() - } - - document.body.addEventListener("keydown", async (e) => { - if (e.target !== document.body) { return } - - if (e.keyCode === 90 && !e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Cmd+Z - Utils.log(`Undo`) - const description = UndoManager.shared.undoDescription - await UndoManager.shared.undo() - if (description) { - FlashMessage.show(`Undo ${description}`) - } else { - FlashMessage.show(`Undo`) - } - } else if (e.keyCode === 90 && e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Shift+Cmd+Z - Utils.log(`Redo`) - const description = UndoManager.shared.redoDescription - await UndoManager.shared.redo() - if (description) { - FlashMessage.show(`Redo ${description}`) - } else { - FlashMessage.show(`Redo`) - } - } - }) - - this.render() - } - - private layoutPage() { - const root = Utils.getElementById("octo-tasks-app") - root.innerText = "" - - const header = root.appendChild(document.createElement("div")) - header.id = "header" - - const main = root.appendChild(document.createElement("div")) - main.id = "main" - - const overlay = root.appendChild(document.createElement("div")) - overlay.id = "overlay" - - const modal = root.appendChild(document.createElement("div")) - modal.id = "modal" - } - - render() { - const { octo, boardTree } = this - const { board, activeView } = boardTree || {} - const mutator = new Mutator(octo) - - const mainElement = Utils.getElementById("main") - - ReactDOM.render( - , - Utils.getElementById("header") - ) - - if (board) { - Utils.setFavicon(board.icon) - document.title = `OCTO - ${board.title} | ${activeView.title}` - } - - ReactDOM.render( - , - mainElement - ) - - if (boardTree && boardTree.board && this.shownCardTree) { - ReactDOM.render( - { this.showCard(undefined) }}>, - Utils.getElementById("overlay") - ) - } else { - ReactDOM.render( -
, - Utils.getElementById("overlay") - ) - } - - if (this.filterAnchorElement) { - const element = this.filterAnchorElement - const bodyRect = document.body.getBoundingClientRect() - const rect = element.getBoundingClientRect() - // Show at bottom-left of element - const maxX = bodyRect.right - 420 - 100 - const pageX = Math.min(maxX, rect.left - bodyRect.left) - const pageY = rect.bottom - bodyRect.top - - ReactDOM.render( - { this.showFilter(undefined) }} - > - , - Utils.getElementById("modal") - ) - } else { - ReactDOM.render(
, Utils.getElementById("modal")) - } - } - - private attachToBoard(boardId: string, viewId?: string) { - this.boardId = boardId - this.viewId = viewId - - this.boardTree = new BoardTree(this.octo, boardId) - - this.boardListener.open(boardId, (blockId: string) => { - console.log(`octoListener.onChanged: ${blockId}`) - this.sync() - }) - - this.sync() - } - - async sync() { - const { workspaceTree, boardTree } = this - - await workspaceTree.sync() - if (boardTree) { - await boardTree.sync() - - // Default to first view - if (!this.viewId) { - this.viewId = boardTree.views[0].id - } - - boardTree.setActiveView(this.viewId) - // TODO: Handle error (viewId not found) - this.viewId = boardTree.activeView.id - console.log(`sync complete... title: ${boardTree.board.title}`) - } - - this.render() - } - - // IPageController - - async showCard(card: IBlock) { - this.cardListener.close() - - if (card) { - const cardTree = new CardTree(this.octo, card.id) - await cardTree.sync() - this.shownCardTree = cardTree - - this.cardListener = new OctoListener() - this.cardListener.open(card.id, async () => { - await cardTree.sync() - this.render() - }) - } else { - this.shownCardTree = undefined - } - - this.render() - } - - showBoard(boardId: string) { - if (this.boardTree?.board?.id === boardId) { return } - - const newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + `?id=${encodeURIComponent(boardId)}` - window.history.pushState({ path: newUrl }, "", newUrl) - - this.attachToBoard(boardId) - } - - showView(viewId: string) { - this.viewId = viewId - this.boardTree.setActiveView(this.viewId) - const newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + `?id=${encodeURIComponent(this.boardId)}&v=${encodeURIComponent(viewId)}` - window.history.pushState({ path: newUrl }, "", newUrl) - this.render() - } - - showFilter(ahchorElement?: HTMLElement) { - this.filterAnchorElement = ahchorElement - this.render() - } - - setSearchText(text?: string) { - this.boardTree.setSearchText(text) - this.render() - } -} - -export { BoardPage } - -const _ = new BoardPage() -console.log("BoardPage") diff --git a/src/client/components/boardComponent.tsx b/src/client/components/boardComponent.tsx index a2ead7735..3e59cc252 100644 --- a/src/client/components/boardComponent.tsx +++ b/src/client/components/boardComponent.tsx @@ -8,18 +8,21 @@ import { CardFilter } from "../cardFilter" import { Constants } from "../constants" import { Menu } from "../menu" import { Mutator } from "../mutator" -import { IBlock, IPageController } from "../octoTypes" +import { IBlock } from "../octoTypes" import { OctoUtils } from "../octoUtils" import { Utils } from "../utils" import { BoardCard } from "./boardCard" import { BoardColumn } from "./boardColumn" -import { Button } from "./button" +import Button from "./button" import { Editable } from "./editable" type Props = { mutator: Mutator, boardTree?: BoardTree - pageController: IPageController + showView: (id: string) => void + showCard: (card: IBlock) => void + showFilter: (el: HTMLElement) => void + setSearchText: (text: string) => void } type State = { @@ -44,7 +47,7 @@ class BoardComponent extends React.Component { } render() { - const { mutator, boardTree, pageController } = this.props + const { mutator, boardTree, showView } = this.props if (!boardTree || !boardTree.board) { return ( @@ -88,7 +91,7 @@ class BoardComponent extends React.Component {
{ mutator.changeTitle(activeView, text) }} /> -
{ OctoUtils.showViewMenu(e, mutator, boardTree, pageController) }}>
+
{ OctoUtils.showViewMenu(e, mutator, boardTree, showView) }}>
{ this.propertiesClicked(e) }}>Properties
{ this.groupByClicked(e) }}> @@ -223,7 +226,7 @@ class BoardComponent extends React.Component { async showCard(card?: IBlock) { console.log(`showCard: ${card?.title}`) - await this.props.pageController.showCard(card) + await this.props.showCard(card) } async addCard(groupByValue?: string) { @@ -270,8 +273,7 @@ class BoardComponent extends React.Component { } private filterClicked(e: React.MouseEvent) { - const { pageController } = this.props - pageController.showFilter(e.target as HTMLElement) + this.props.showFilter(e.target as HTMLElement) } private async optionsClicked(e: React.MouseEvent) { @@ -403,13 +405,13 @@ class BoardComponent extends React.Component { if (e.keyCode === 27) { // ESC: Clear search this.searchFieldRef.current.text = "" this.setState({ ...this.state, isSearching: false }) - this.props.pageController.setSearchText(undefined) + this.props.setSearchText(undefined) e.preventDefault() } } searchChanged(text?: string) { - this.props.pageController.setSearchText(text) + this.props.setSearchText(text) } } diff --git a/src/client/components/button.scss b/src/client/components/button.scss new file mode 100644 index 000000000..e8fe5fec7 --- /dev/null +++ b/src/client/components/button.scss @@ -0,0 +1,17 @@ +.Button { + text-align: center; + border-radius: 5px; + padding: 0 5px; + min-width: 20px; + cursor: pointer; + overflow: hidden; + + transition: background 100ms ease-out 0s; + + &:hover { + background-color: #eeeeee; + } + .octo-hovercontrol { + background: rgb(239, 239, 238); + } +} diff --git a/src/client/components/button.tsx b/src/client/components/button.tsx index 46d99f10a..2ee2874a9 100644 --- a/src/client/components/button.tsx +++ b/src/client/components/button.tsx @@ -1,5 +1,7 @@ import React from "react" +import './button.scss' + type Props = { onClick?: (e: React.MouseEvent) => void style?: React.CSSProperties @@ -8,13 +10,13 @@ type Props = { title?: string } -class Button extends React.Component { +export default class Button extends React.Component { render() { const style = {...this.props.style, backgroundColor: this.props.backgroundColor} return (
{this.props.children} @@ -22,5 +24,3 @@ class Button extends React.Component {
) } } - -export { Button } diff --git a/src/client/components/cardDialog.tsx b/src/client/components/cardDialog.tsx index ff486f341..2a47ac310 100644 --- a/src/client/components/cardDialog.tsx +++ b/src/client/components/cardDialog.tsx @@ -9,7 +9,7 @@ import { IBlock } from "../octoTypes" import { OctoUtils } from "../octoUtils" import { PropertyMenu } from "../propertyMenu" import { Utils } from "../utils" -import { Button } from "./button" +import Button from "./button" import { Editable } from "./editable" import { MarkdownEditor } from "./markdownEditor" diff --git a/src/client/components/sidebar.tsx b/src/client/components/sidebar.tsx index 5ce027fdc..adace5a11 100644 --- a/src/client/components/sidebar.tsx +++ b/src/client/components/sidebar.tsx @@ -9,7 +9,7 @@ import { WorkspaceTree } from "../workspaceTree" type Props = { mutator: Mutator - pageController: IPageController + showBoard: (id: string) => void workspaceTree: WorkspaceTree, boardTree?: BoardTree } @@ -18,6 +18,10 @@ class Sidebar extends React.Component { render() { const { workspaceTree } = this.props + if (!workspaceTree) { + return
+ } + const { boards } = workspaceTree return ( @@ -47,7 +51,7 @@ class Sidebar extends React.Component { } private showOptions(e: React.MouseEvent, board: Board) { - const { mutator, pageController, workspaceTree } = this.props + const { mutator, showBoard, workspaceTree } = this.props const { boards } = workspaceTree const options: MenuOption[] = [] @@ -64,8 +68,8 @@ class Sidebar extends React.Component { mutator.deleteBlock( board, "delete block", - async () => { pageController.showBoard(nextBoardId!) }, - async () => { pageController.showBoard(board.id) }, + async () => { showBoard(nextBoardId!) }, + async () => { showBoard(board.id) }, ) break } @@ -104,20 +108,19 @@ class Sidebar extends React.Component { } private boardClicked(board: Board) { - const { pageController } = this.props - pageController.showBoard(board.id) + this.props.showBoard(board.id) } async addBoardClicked() { - const { mutator, boardTree, pageController } = this.props + const { mutator, boardTree, showBoard } = this.props const oldBoardId = boardTree?.board?.id const board = new Board() await mutator.insertBlock( board, "add board", - async () => { pageController.showBoard(board.id) }, - async () => { if (oldBoardId) { pageController.showBoard(oldBoardId) } }) + async () => { showBoard(board.id) }, + async () => { if (oldBoardId) { showBoard(oldBoardId) } }) await mutator.insertBlock(board) } diff --git a/src/client/components/tableComponent.tsx b/src/client/components/tableComponent.tsx index 4179aea1e..988fd133b 100644 --- a/src/client/components/tableComponent.tsx +++ b/src/client/components/tableComponent.tsx @@ -7,17 +7,20 @@ import { BoardTree } from "../boardTree" import { CsvExporter } from "../csvExporter" import { Menu } from "../menu" import { Mutator } from "../mutator" -import { IBlock, IPageController } from "../octoTypes" +import { IBlock } from "../octoTypes" import { OctoUtils } from "../octoUtils" import { Utils } from "../utils" -import { Button } from "./button" +import Button from "./button" import { Editable } from "./editable" import { TableRow } from "./tableRow" type Props = { mutator: Mutator, boardTree?: BoardTree - pageController: IPageController + showView: (id: string) => void + showCard: (card: IBlock) => void + showFilter: (el: HTMLElement) => void + setSearchText: (text: string) => void } type State = { @@ -43,7 +46,7 @@ class TableComponent extends React.Component { } render() { - const { mutator, boardTree, pageController } = this.props + const { mutator, boardTree, showView } = this.props if (!boardTree || !boardTree.board) { return ( @@ -85,11 +88,11 @@ class TableComponent extends React.Component {
{ mutator.changeTitle(activeView, text) }} /> -
{ OctoUtils.showViewMenu(e, mutator, boardTree, pageController) }}>
+
{ OctoUtils.showViewMenu(e, mutator, boardTree, showView) }}>
{ this.propertiesClicked(e) }}>Properties
-
{ this.filterClicked(e) }}>Filter
-
{ OctoUtils.showSortMenu(e, mutator, boardTree) }}>Sort
+
{ this.filterClicked(e) }}>Filter
+
{ OctoUtils.showSortMenu(e, mutator, boardTree) }}>Sort
{this.state.isSearching ? { } private filterClicked(e: React.MouseEvent) { - const { pageController } = this.props - pageController.showFilter(e.target as HTMLElement) + this.props.showFilter(e.target as HTMLElement) } private async optionsClicked(e: React.MouseEvent) { @@ -348,7 +350,7 @@ class TableComponent extends React.Component { async showCard(card: IBlock) { console.log(`showCard: ${card.title}`) - await this.props.pageController.showCard(card) + await this.props.showCard(card) } focusOnCardTitle(cardId: string) { @@ -396,13 +398,13 @@ class TableComponent extends React.Component { if (e.keyCode === 27) { // ESC: Clear search this.searchFieldRef.current.text = "" this.setState({ ...this.state, isSearching: false }) - this.props.pageController.setSearchText(undefined) + this.props.setSearchText(undefined) e.preventDefault() } } searchChanged(text?: string) { - this.props.pageController.setSearchText(text) + this.props.setSearchText(text) } } diff --git a/src/client/components/workspaceComponent.tsx b/src/client/components/workspaceComponent.tsx index 30342b2b4..eed77b353 100644 --- a/src/client/components/workspaceComponent.tsx +++ b/src/client/components/workspaceComponent.tsx @@ -1,7 +1,7 @@ import React from "react" import { BoardTree } from "../boardTree" import { Mutator } from "../mutator" -import { IPageController } from "../octoTypes" +import { IBlock } from "../octoTypes" import { Utils } from "../utils" import { WorkspaceTree } from "../workspaceTree" import { BoardComponent } from "./boardComponent" @@ -12,16 +12,21 @@ type Props = { mutator: Mutator, workspaceTree: WorkspaceTree boardTree?: BoardTree - pageController: IPageController + showBoard: (id: string) => void + showView: (id: string) => void + showCard: (card: IBlock) => void + showFilter: (el: HTMLElement) => void + setSearchText: (text: string) => void } class WorkspaceComponent extends React.Component { render() { - const { mutator, boardTree, workspaceTree, pageController } = this.props + const { mutator, boardTree, workspaceTree, showBoard } = this.props + Utils.assert(workspaceTree) const element =
- + {this.mainComponent()}
@@ -29,7 +34,7 @@ class WorkspaceComponent extends React.Component { } private mainComponent() { - const { mutator, boardTree, pageController } = this.props + const { mutator, boardTree, showCard, showFilter, setSearchText, showView } = this.props const { activeView } = boardTree || {} if (!activeView) { @@ -38,11 +43,11 @@ class WorkspaceComponent extends React.Component { switch (activeView?.viewType) { case "board": { - return + return } case "table": { - return + return } default: { diff --git a/src/client/main.tsx b/src/client/main.tsx new file mode 100644 index 000000000..b5f54c60d --- /dev/null +++ b/src/client/main.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import App from './app'; + +ReactDOM.render(, document.getElementById('octo-tasks-app')); diff --git a/src/client/octoUtils.tsx b/src/client/octoUtils.tsx index 0df9702e0..474065de0 100644 --- a/src/client/octoUtils.tsx +++ b/src/client/octoUtils.tsx @@ -5,11 +5,11 @@ import { BoardView, ISortOption } from "./boardView" import { Editable } from "./components/editable" import { Menu, MenuOption } from "./menu" import { Mutator } from "./mutator" -import { IBlock, IPageController } from "./octoTypes" +import { IBlock } from "./octoTypes" import { Utils } from "./utils" class OctoUtils { - static async showViewMenu(e: React.MouseEvent, mutator: Mutator, boardTree: BoardTree, pageController: IPageController) { + static async showViewMenu(e: React.MouseEvent, mutator: Mutator, boardTree: BoardTree, showView: (id: string) => void) { const { board } = boardTree const options: MenuOption[] = boardTree.views.map(view => ({ id: view.id, name: view.title || "Untitled View" })) @@ -33,7 +33,7 @@ class OctoUtils { const view = boardTree.activeView const nextView = boardTree.views.find(o => o !== view) await mutator.deleteBlock(view, "delete view") - pageController.showView(nextView.id) + showView(nextView.id) break } case "__addview-board": { @@ -48,8 +48,8 @@ class OctoUtils { await mutator.insertBlock( view, "add view", - async () => { pageController.showView(view.id) }, - async () => { pageController.showView(oldViewId) }) + async () => { showView(view.id) }, + async () => { showView(oldViewId) }) break } case "__addview-table": { @@ -65,13 +65,13 @@ class OctoUtils { await mutator.insertBlock( view, "add view", - async () => { pageController.showView(view.id) }, - async () => { pageController.showView(oldViewId) }) + async () => { showView(view.id) }, + async () => { showView(oldViewId) }) break } default: { const view = boardTree.views.find(o => o.id === optionId) - pageController.showView(view.id) + showView(view.id) } } } diff --git a/src/client/pages/boardPage.tsx b/src/client/pages/boardPage.tsx new file mode 100644 index 000000000..ad4ee5f03 --- /dev/null +++ b/src/client/pages/boardPage.tsx @@ -0,0 +1,256 @@ +import React from "react" +import ReactDOM from "react-dom" +import { BoardTree } from "../boardTree" +import { BoardView } from "../boardView" +import { CardTree } from "../cardTree" +import { CardDialog } from "../components/cardDialog" +import { FilterComponent } from "../components/filterComponent" +import { WorkspaceComponent } from "../components/workspaceComponent" +import { FlashMessage } from "../flashMessage" +import { Mutator } from "../mutator" +import { OctoClient } from "../octoClient" +import { OctoListener } from "../octoListener" +import { IBlock } from "../octoTypes" +import { UndoManager } from "../undomanager" +import { Utils } from "../utils" +import { WorkspaceTree } from "../workspaceTree" + +type Props = { +} + +type State = { + boardId: string + viewId: string + workspaceTree: WorkspaceTree + boardTree?: BoardTree + shownCardTree?: CardTree +} + +export default class BoardPage extends React.Component { + view: BoardView + + updateTitleTimeout: number + updatePropertyLabelTimeout: number + + private filterAnchorElement?: HTMLElement + private octo = new OctoClient() + private boardListener = new OctoListener() + private cardListener = new OctoListener() + + constructor(props: Props) { + super(props) + const queryString = new URLSearchParams(window.location.search) + const boardId = queryString.get("id") + const viewId = queryString.get("v") + + this.state = { + boardId, + viewId, + workspaceTree: new WorkspaceTree(this.octo), + } + + Utils.log(`BoardPage. boardId: ${boardId}`) + } + + componentDidUpdate(prevProps: Props, prevState: State) { + Utils.log(`componentDidUpdate`) + const board = this.state.boardTree?.board + const prevBoard = prevState.boardTree?.board + + const activeView = this.state.boardTree?.activeView + const prevActiveView = prevState.boardTree?.activeView + + if (board?.icon !== prevBoard?.icon) { + Utils.setFavicon(board?.icon) + } + if (board?.title !== prevBoard?.title || activeView?.title !== prevActiveView?.title) { + document.title = `OCTO - ${board?.title} | ${activeView?.title}` + } + } + + undoRedoHandler = async (e: KeyboardEvent) => { + if (e.target !== document.body) { return } + + if (e.keyCode === 90 && !e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Cmd+Z + Utils.log(`Undo`) + const description = UndoManager.shared.undoDescription + await UndoManager.shared.undo() + if (description) { + FlashMessage.show(`Undo ${description}`) + } else { + FlashMessage.show(`Undo`) + } + } else if (e.keyCode === 90 && e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) { // Shift+Cmd+Z + Utils.log(`Redo`) + const description = UndoManager.shared.redoDescription + await UndoManager.shared.redo() + if (description) { + FlashMessage.show(`Redo ${description}`) + } else { + FlashMessage.show(`Redo`) + } + } + } + + componentDidMount() { + document.addEventListener("keydown", this.undoRedoHandler) + if (this.state.boardId) { + this.attachToBoard(this.state.boardId, this.state.viewId) + } else { + this.sync() + } + } + + componentWillUnmount() { + document.removeEventListener("keydown", this.undoRedoHandler) + } + + render() { + const { workspaceTree, shownCardTree } = this.state + const { board, activeView } = this.state.boardTree || {} + const mutator = new Mutator(this.octo) + + // TODO Move all this into the root portal component when that is merged + if (this.state.boardTree && this.state.boardTree.board && shownCardTree) { + ReactDOM.render( + { this.showCard(undefined) }}>, + Utils.getElementById("overlay") + ) + } else { + const overlay = document.getElementById("overlay") + if (overlay) { + ReactDOM.render( +
, + overlay + ) + } + } + + if (this.filterAnchorElement) { + const element = this.filterAnchorElement + const bodyRect = document.body.getBoundingClientRect() + const rect = element.getBoundingClientRect() + // Show at bottom-left of element + const maxX = bodyRect.right - 420 - 100 + const pageX = Math.min(maxX, rect.left - bodyRect.left) + const pageY = rect.bottom - bodyRect.top + + ReactDOM.render( + { this.showFilter(undefined) }} + > + , + Utils.getElementById("modal") + ) + } else { + const modal = document.getElementById("modal") + if (modal) { + ReactDOM.render(
, modal) + } + } + + Utils.log(`BoardPage.render ${this.state.boardTree?.board?.title}`) + return ( +
+ { this.showView(id) }} + showCard={(card) => { this.showCard(card) }} + showBoard={(id) => { this.showBoard(id) }} + showFilter={(el) => { this.showFilter(el) }} + setSearchText={(text) => { this.setSearchText(text) }} /> +
+ ) + } + + private async attachToBoard(boardId: string, viewId?: string) { + Utils.log(`attachToBoard: ${boardId}`) + + this.boardListener.open(boardId, (blockId: string) => { + console.log(`octoListener.onChanged: ${blockId}`) + this.sync(boardId) + }) + + this.sync(boardId, viewId) + } + + async sync(boardId: string = this.state.boardId, viewId: string | undefined = this.state.viewId) { + const { workspaceTree } = this.state + Utils.log(`sync start: ${boardId}`) + + await workspaceTree.sync() + + if (boardId) { + const boardTree = new BoardTree(this.octo, boardId) + await boardTree.sync() + + // Default to first view + if (!viewId) { + viewId = boardTree.views[0].id + } + + boardTree.setActiveView(viewId) + // TODO: Handle error (viewId not found) + this.setState({ + ...this.state, + boardTree, + viewId: boardTree.activeView.id + }) + Utils.log(`sync complete: ${boardTree.board.id} (${boardTree.board.title})`) + } else { + this.forceUpdate() + } + } + + // IPageController + + async showCard(card: IBlock) { + this.cardListener.close() + + if (card) { + const cardTree = new CardTree(this.octo, card.id) + await cardTree.sync() + this.setState({...this.state, shownCardTree: cardTree}) + + this.cardListener = new OctoListener() + this.cardListener.open(card.id, async () => { + await cardTree.sync() + this.forceUpdate() + }) + } else { + this.setState({...this.state, shownCardTree: undefined}) + } + } + + showBoard(boardId: string) { + const { boardTree } = this.state + + if (boardTree?.board?.id === boardId) { return } + + const newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + `?id=${encodeURIComponent(boardId)}` + window.history.pushState({ path: newUrl }, "", newUrl) + + this.attachToBoard(boardId) + } + + showView(viewId: string) { + this.state.boardTree.setActiveView(viewId) + this.setState({ viewId, boardTree: this.state.boardTree }) + const newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + `?id=${encodeURIComponent(this.state.boardId)}&v=${encodeURIComponent(viewId)}` + window.history.pushState({ path: newUrl }, "", newUrl) + } + + showFilter(ahchorElement?: HTMLElement) { + this.filterAnchorElement = ahchorElement + } + + setSearchText(text?: string) { + this.state.boardTree?.setSearchText(text) + } +} diff --git a/src/client/pages/homePage.tsx b/src/client/pages/homePage.tsx new file mode 100644 index 000000000..df2b1e762 --- /dev/null +++ b/src/client/pages/homePage.tsx @@ -0,0 +1,76 @@ +import React from 'react'; + +import { IBlock } from "../octoTypes" +import { Archiver } from "../archiver" +import { Board } from "../board" +import { Mutator } from "../mutator" +import { OctoClient } from "../octoClient" +import { UndoManager } from "../undomanager" +import { Utils } from "../utils" +import Button from '../components/button'; + +type Props = {}; + +type State = { + boards: IBlock[]; +}; + +export default class HomePage extends React.Component { + constructor(props: Props) { + super(props); + this.state = { + boards: [], + } + } + + componentDidMount() { + this.loadBoards(); + } + + loadBoards = async () => { + const octo = new OctoClient() + const boards = await octo.getBlocks(null, "board") + this.setState({boards}); + } + + importClicked = async () => { + const octo = new OctoClient() + const mutator = new Mutator(octo, UndoManager.shared) + Archiver.importFullArchive(mutator, () => { + this.loadBoards() + }) + } + + exportClicked = async () => { + const octo = new OctoClient() + const mutator = new Mutator(octo, UndoManager.shared) + Archiver.exportFullArchive(mutator) + } + + addClicked = async () => { + const octo = new OctoClient() + const board = new Board() + await octo.insertBlock(board) + } + + public render(): React.ReactNode { + return ( +
+ +
+ +
+ + {this.state.boards.map((board) => ( +

+ + {board.icon && {board.icon}} + {board.title} + {Utils.displayDate(new Date(board.updateAt))} + +

+ ))} +
+ ); + } +} diff --git a/src/client/pages/loginPage.tsx b/src/client/pages/loginPage.tsx new file mode 100644 index 000000000..b37203600 --- /dev/null +++ b/src/client/pages/loginPage.tsx @@ -0,0 +1,37 @@ +import React from "react" + +type Props = {} + +type State = { + username: string; + password: string; +} + +export default class LoginPage extends React.Component { + state = { + username: '', + password: '', + } + + handleLogin = () => { + console.log("Logging in"); + } + + public render(): React.ReactNode { + return ( +
+ + this.setState({username: e.target.value})} + /> + + + +
+ ) + } +} diff --git a/src/static/main.css b/src/static/main.css index 512da8390..893d8b8ec 100644 --- a/src/static/main.css +++ b/src/static/main.css @@ -73,7 +73,9 @@ hr { overflow: auto; } -#octo-tasks-app > #main { +#octo-tasks-app #frame, +#octo-tasks-app #main, +#octo-tasks-app .BoardPage { flex: 1 1 auto; display: flex; flex-direction: column; diff --git a/webpack.common.js b/webpack.common.js index ccf4d613f..3b622b122 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -26,6 +26,14 @@ function makeCommonConfig() { loader: "file-loader", }, { + test: /\.s[ac]ss$/i, + use: [ + 'style-loader', + 'css-loader', + 'sass-loader', + ], + }, + { test: /\.(tsx?|js|jsx|html)$/, use: [ ], @@ -50,13 +58,13 @@ function makeCommonConfig() { new HtmlWebpackPlugin({ inject: true, title: "OCTO", - chunks: ["boardPage"], + chunks: ["main"], template: "html-templates/page.ejs", - filename: 'board.html' + filename: 'index.html' }), ], entry: { - boardPage: "./src/client/boardPage.tsx" + main: "./src/client/main.tsx", }, output: { filename: "[name].js",