diff --git a/.gitignore b/.gitignore index 801e1dd..699692c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ yarn-debug.log* yarn-error.log* .vscode/ *.code-workspace +package-lock.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e8ae18b..49c19ce 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ variables: GIT_SUBMODULE_STRATEGY: normal DOCKER_TAG: ${CI_COMMIT_BRANCH} DOCKER_IMAGE: ${CI_REGISTRY_IMAGE} + FF_GITLAB_REGISTRY_HELPER_IMAGE: 1 cache: untracked: true diff --git a/package-lock.json b/package-lock.json index 3931793..fbb7f6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1109,9 +1109,9 @@ } }, "@babel/runtime-corejs3": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz", - "integrity": "sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.13.tgz", + "integrity": "sha512-8fSpqYRETHATtNitsCXq8QQbKJP31/KnDl2Wz2Vtui9nKzjss2ysuZtyVsWjBtvkeEFo346gkwjYPab1hvrXkQ==", "requires": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" @@ -1165,6 +1165,11 @@ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" }, + "@braintree/sanitize-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-5.0.0.tgz", + "integrity": "sha512-WmKrB/575EJCzbeSJR3YQ5sET5FaizeljLRw1382qVUeGqzuWBgIS+AF5a0FO51uQTrDpoRgvuHC2IWVsgwkkA==" + }, "@cnakazawa/watch": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", @@ -1909,6 +1914,19 @@ } } }, + "@kyleshockey/object-assign-deep": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@kyleshockey/object-assign-deep/-/object-assign-deep-0.4.2.tgz", + "integrity": "sha1-hJAPDu/DcnmPR1G1JigwuCCJIuw=" + }, + "@kyleshockey/xml": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@kyleshockey/xml/-/xml-1.0.2.tgz", + "integrity": "sha512-iMo32MPLcI9cPxs3YL5kmKxKgDmkSZDCFEqIT5eRk7d/Ll8r4X3SwGYSigzALd6+RHWlFEmjL1QyaQ15xDZFlw==", + "requires": { + "stream": "^0.0.2" + } + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -2269,6 +2287,14 @@ "@types/node": "*" } }, + "@types/hast": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz", + "integrity": "sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q==", + "requires": { + "@types/unist": "*" + } + }, "@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -2410,6 +2436,11 @@ "source-map": "^0.6.1" } }, + "@types/unist": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", + "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==" + }, "@types/warning": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", @@ -3108,6 +3139,14 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, + "autolinker": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-3.14.2.tgz", + "integrity": "sha512-VO66nXUCZFxTq7fVHAaiAkZNXRQ1l3IFi6D5P7DLoyIEAn2E8g7TWbyEgLlz1uW74LfWmu1A17IPWuPQyGuNVg==", + "requires": { + "tslib": "^1.9.3" + } + }, "autoprefixer": { "version": "9.8.6", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", @@ -3849,6 +3888,11 @@ "node-int64": "^0.4.0" } }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" + }, "buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -4114,6 +4158,21 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, + "character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==" + }, + "character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==" + }, + "character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -4219,6 +4278,17 @@ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, + "clipboard": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz", + "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==", + "optional": true, + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -4339,6 +4409,11 @@ "delayed-stream": "~1.0.0" } }, + "comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==" + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -4520,6 +4595,14 @@ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, + "copy-to-clipboard": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "requires": { + "toggle-selection": "^1.0.6" + } + }, "core-js": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", @@ -4604,6 +4687,30 @@ "sha.js": "^2.4.8" } }, + "create-react-class": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz", + "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, + "cross-fetch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz", + "integrity": "sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ==", + "requires": { + "node-fetch": "2.6.1" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + } + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -4769,6 +4876,11 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==" }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=" + }, "cssdb": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", @@ -5245,6 +5357,12 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "optional": true + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -5464,6 +5582,11 @@ "domelementtype": "1" } }, + "dompurify": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.2.6.tgz", + "integrity": "sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ==" + }, "domutils": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", @@ -5561,6 +5684,11 @@ } } }, + "emitter-component": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz", + "integrity": "sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY=" + }, "emittery": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", @@ -5583,7 +5711,7 @@ }, "encoding": { "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "resolved": false, "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { "iconv-lite": "~0.4.13" @@ -5718,6 +5846,17 @@ "ext": "^1.1.2" } }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -6390,6 +6529,15 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -6710,6 +6858,11 @@ "picomatch": "^2.2.1" } }, + "fast-json-patch": { + "version": "3.0.0-1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.0.0-1.tgz", + "integrity": "sha512-6pdFb07cknxvPzCeLsFHStEy+MysPJPgZQ9LbQ/2O67unQF93SNqfdSqnPPl71YMHX+AD8gbl7iuoGFzHEdDuw==" + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6738,6 +6891,14 @@ "reusify": "^1.0.4" } }, + "fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "requires": { + "format": "^0.2.0" + } + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -7093,6 +7254,11 @@ "mime-types": "^2.1.12" } }, + "format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=" + }, "formidable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", @@ -7385,6 +7551,15 @@ "minimatch": "~3.0.2" } }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "optional": true, + "requires": { + "delegate": "^3.1.2" + } + }, "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", @@ -7562,6 +7737,23 @@ "minimalistic-assert": "^1.0.1" } }, + "hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==" + }, + "hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "requires": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -7572,6 +7764,11 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, + "highlight.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.5.0.tgz", + "integrity": "sha512-xTmvd9HiIHR6L53TMC7TKolEj65zG1XU+Onr8oi86mYa+nLcIbxTTWkpW7CsEwv/vK7u1zb8alZIMLDqqN6KTw==" + }, "history": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", @@ -7988,6 +8185,11 @@ "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.9.tgz", "integrity": "sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A==" }, + "immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=" + }, "import-cwd": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", @@ -8168,6 +8370,20 @@ } } }, + "is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" + }, + "is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, "is-arguments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", @@ -8248,6 +8464,11 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, + "is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==" + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -8275,6 +8496,15 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==" }, + "is-dom": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-dom/-/is-dom-1.1.0.tgz", + "integrity": "sha512-u82f6mvhYxRPKpw8V1N0W8ce1xXwOrQtgGcxl6UCL5zBmZu3is/18K0rR7uFCnMDuAsS/3W54mGL4vsaFUQlEQ==", + "requires": { + "is-object": "^1.0.1", + "is-window": "^1.0.2" + } + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -8313,6 +8543,11 @@ "is-extglob": "^2.1.1" } }, + "is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" + }, "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -8333,6 +8568,11 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, + "is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==" + }, "is-path-cwd": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", @@ -8436,6 +8676,11 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, + "is-window": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz", + "integrity": "sha1-LIlspT25feRdPDMTOmXYyfVjSA0=" + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -8473,6 +8718,14 @@ "whatwg-fetch": ">=0.10.0" } }, + "isomorphic-form-data": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-form-data/-/isomorphic-form-data-2.0.0.tgz", + "integrity": "sha512-TYgVnXWeESVmQSg4GLVbalmQ+B4NPi/H4eWxqALKj63KsUrcu301YDjBqaOw3h+cbak7Na4Xyps3BiptHtxTfg==", + "requires": { + "form-data": "^2.3.2" + } + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -10309,6 +10562,11 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz", "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==" }, + "js-file-download": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz", + "integrity": "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -10737,7 +10995,7 @@ }, "libcimsvg": { "version": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git#7e5c48fff7eced878da471b5c69ab4a8b575a6c9", - "from": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git" + "from": "libcimsvg@git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git" }, "lie": { "version": "3.3.0", @@ -10898,6 +11156,15 @@ "tslib": "^1.10.0" } }, + "lowlight": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.18.0.tgz", + "integrity": "sha512-Zlc3GqclU71HRw5fTOy00zz5EOlqAdKMYhOFIO8ay4SQEDQgFuhR8JNwDIzAGMLoqTsWxe0elUNmq5o2USRAzw==", + "requires": { + "fault": "^1.0.0", + "highlight.js": "~10.5.0" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -10906,6 +11173,14 @@ "yallist": "^4.0.0" } }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "requires": { + "es5-ext": "~0.10.2" + } + }, "magic-string": { "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", @@ -10986,6 +11261,33 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, + "memoizee": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", + "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.53", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "dependencies": { + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + } + } + }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -11334,6 +11636,11 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" }, + "moment-duration-format": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/moment-duration-format/-/moment-duration-format-2.3.2.tgz", + "integrity": "sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -12211,6 +12518,19 @@ "safe-buffer": "^5.1.1" } }, + "parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, "parse-json": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", @@ -13514,6 +13834,14 @@ } } }, + "prismjs": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz", + "integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==", + "requires": { + "clipboard": "^2.0.0" + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -13570,6 +13898,14 @@ "warning": "^4.0.0" } }, + "property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "requires": { + "xtend": "^4.0.0" + } + }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -13678,6 +14014,11 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "querystring-browser": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/querystring-browser/-/querystring-browser-1.0.4.tgz", + "integrity": "sha1-8uNYgYQKgZvHsb9Zf68JeeZiLcY=" + }, "querystring-es3": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", @@ -14045,6 +14386,15 @@ "prop-types": "^15.6.2" } }, + "react-copy-to-clipboard": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.3.tgz", + "integrity": "sha512-9S3j+m+UxDZOM0Qb8mhnT/rMR0NGSrj9A/073yz2DSxPMYhmYFBMYIdI2X4o8AjOjyFsSNxDRnCX6s/gRxpriw==", + "requires": { + "copy-to-clipboard": "^3", + "prop-types": "^15.5.8" + } + }, "react-d3": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/react-d3/-/react-d3-0.4.0.tgz", @@ -14054,6 +14404,15 @@ "react": ">0.12.0" } }, + "react-debounce-input": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/react-debounce-input/-/react-debounce-input-3.2.3.tgz", + "integrity": "sha512-7Bfjm9sxrtgB+IsSrdXoo4CVqKg7CbWC68dNhr8q7ZmY6C0AqtR524//SenHQWT+eeSG9DmSLWNWCUFSyaaWSQ==", + "requires": { + "lodash.debounce": "^4", + "prop-types": "^15.7.2" + } + }, "react-dev-utils": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.1.tgz", @@ -14238,6 +14597,29 @@ "prop-types": "^15.7.2" } }, + "react-immutable-proptypes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", + "integrity": "sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==", + "requires": { + "invariant": "^2.2.2" + } + }, + "react-immutable-pure-component": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/react-immutable-pure-component/-/react-immutable-pure-component-1.2.4.tgz", + "integrity": "sha512-zPXaFWxaF4+ztVMFNMlCFkrhjpb9MPcL3JnXUpb6wKGF1+vBoSgClFbpbOsZAji7gm+RHBE24H44Lday2xxPjw==" + }, + "react-inspector": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-2.3.1.tgz", + "integrity": "sha512-tUUK7t3KWgZEIUktOYko5Ic/oYwvjEvQUFAGC1UeMeDaQ5za2yZFtItJa2RTwBJB//NxPr000WQK6sEbqC6y0Q==", + "requires": { + "babel-runtime": "^6.26.0", + "is-dom": "^1.0.9", + "prop-types": "^15.6.1" + } + }, "react-is": { "version": "16.8.6", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", @@ -14259,6 +14641,23 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-motion": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/react-motion/-/react-motion-0.5.2.tgz", + "integrity": "sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==", + "requires": { + "performance-now": "^0.2.0", + "prop-types": "^15.5.8", + "raf": "^3.1.0" + }, + "dependencies": { + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + } + } + }, "react-notification-system": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/react-notification-system/-/react-notification-system-0.4.0.tgz", @@ -14427,6 +14826,18 @@ "transformation-matrix": "^2.2.0" } }, + "react-syntax-highlighter": { + "version": "15.4.3", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.4.3.tgz", + "integrity": "sha512-TnhGgZKXr5o8a63uYdRTzeb8ijJOgRGe0qjrE0eK/gajtdyqnSO6LqB3vW16hHB0cFierYSoy/AOJw8z1Dui8g==", + "requires": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "lowlight": "^1.17.0", + "prismjs": "^1.22.0", + "refractor": "^3.2.0" + } + }, "react-textarea-autosize": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz", @@ -14591,6 +15002,24 @@ "symbol-observable": "^1.2.0" } }, + "redux-immutable": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-3.1.0.tgz", + "integrity": "sha1-yvvWhuBxEmERm5wolgk13EeknQo=", + "requires": { + "immutable": "^3.8.1" + } + }, + "refractor": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.3.1.tgz", + "integrity": "sha512-vaN6R56kLMuBszHSWlwTpcZ8KTMG6aUCok4GrxYDT20UIOXxOc5o6oDc8tNTzSlH3m2sI+Eu9Jo2kVdDcUTWYw==", + "requires": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.23.0" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -14718,6 +15147,15 @@ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" }, + "remarkable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz", + "integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==", + "requires": { + "argparse": "^1.0.10", + "autolinker": "^3.11.0" + } + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -14880,6 +15318,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz", + "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==" + }, "resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -15452,6 +15895,12 @@ } } }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", + "optional": true + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -15517,6 +15966,11 @@ } } }, + "serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=" + }, "serialize-javascript": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", @@ -15949,6 +16403,11 @@ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, + "space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==" + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -16113,6 +16572,14 @@ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, + "stream": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.2.tgz", + "integrity": "sha1-f1Nj8Ff2WSxVlfALyAon9c7B8O8=", + "requires": { + "emitter-component": "^1.1.1" + } + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -16439,6 +16906,116 @@ "util.promisify": "~1.0.0" } }, + "swagger-client": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.12.2.tgz", + "integrity": "sha512-l4aAty0VNmHaOEEvOI7Tc6xtUZLNxtFEOZzansjxHjbV9o2ZsiFPMdomIGj14n/Zb0S2eN83cEJYHJ3SENUYPw==", + "requires": { + "@babel/runtime-corejs3": "^7.11.2", + "btoa": "^1.2.1", + "buffer": "^6.0.3", + "cookie": "~0.4.1", + "cross-fetch": "^3.0.6", + "deep-extend": "~0.6.0", + "fast-json-patch": "^3.0.0-1", + "isomorphic-form-data": "~2.0.0", + "js-yaml": "^3.14.0", + "lodash": "^4.17.19", + "qs": "^6.9.4", + "querystring-browser": "^1.0.4", + "traverse": "~0.6.6", + "url": "~0.11.0" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" + } + } + }, + "swagger-ui-react": { + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-3.42.0.tgz", + "integrity": "sha512-uxoM5UxmjSIcOsAvvD2yKirRR4tCqJMiEklDMgg68JuoFsrpKk9RlUm5A2rI2dA8HdpBO1dIUA8zHdIdpMF5aA==", + "requires": { + "@babel/runtime-corejs3": "^7.12.13", + "@braintree/sanitize-url": "^5.0.0", + "@kyleshockey/object-assign-deep": "^0.4.2", + "@kyleshockey/xml": "^1.0.2", + "base64-js": "^1.5.1", + "classnames": "^2.2.6", + "css.escape": "1.5.1", + "deep-extend": "0.6.0", + "dompurify": "^2.2.6", + "ieee754": "^1.2.1", + "immutable": "^3.x.x", + "js-file-download": "^0.4.12", + "js-yaml": "^3.13.1", + "lodash": "^4.17.20", + "memoizee": "^0.4.15", + "prop-types": "^15.7.2", + "randombytes": "^2.1.0", + "react-copy-to-clipboard": "5.0.3", + "react-debounce-input": "^3.2.3", + "react-immutable-proptypes": "2.2.0", + "react-immutable-pure-component": "^1.1.1", + "react-inspector": "^2.3.0", + "react-motion": "^0.5.2", + "react-redux": "=4.4.10", + "react-syntax-highlighter": "^15.4.3", + "redux": "=3.7.2", + "redux-immutable": "3.1.0", + "remarkable": "^2.0.1", + "reselect": "^4.0.0", + "serialize-error": "^2.1.0", + "sha.js": "^2.4.11", + "swagger-client": "^3.12.2", + "url-parse": "^1.4.7", + "xml-but-prettier": "^1.0.1", + "zenscroll": "^4.0.2" + }, + "dependencies": { + "react-redux": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-4.4.10.tgz", + "integrity": "sha512-tjL0Bmpkj75Td0k+lXlF8Fc8a9GuXFv/3ahUOCXExWs/jhsKiQeTffdH0j5byejCGCRL4tvGFYlrwBF1X/Aujg==", + "requires": { + "create-react-class": "^15.5.1", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.0.0", + "lodash": "^4.17.11", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2" + } + }, + "redux": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", + "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "requires": { + "lodash": "^4.2.1", + "lodash-es": "^4.2.1", + "loose-envify": "^1.1.0", + "symbol-observable": "^1.0.3" + } + } + } + }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -16663,11 +17240,26 @@ "setimmediate": "^1.0.4" } }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "timsort": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "optional": true + }, "tiny-invariant": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", @@ -16735,6 +17327,11 @@ "is-number": "^7.0.0" } }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -16763,6 +17360,11 @@ "resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.5.0.tgz", "integrity": "sha512-Z9UotSHOWMcqYYKwQUts49CjCH8H51HBbPN4uiQIwNpGMZs70LpugThBmLpUy2LFdrbfXGhdlu4WJ3yILK6kcw==" }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" + }, "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", @@ -18827,6 +19429,14 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==" }, + "xml-but-prettier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-but-prettier/-/xml-but-prettier-1.0.1.tgz", + "integrity": "sha1-9aMyZ+1CzNTjVcYlV6XjmwH7QPM=", + "requires": { + "repeat-string": "^1.5.2" + } + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", @@ -18957,6 +19567,11 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, + "zenscroll": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zenscroll/-/zenscroll-4.0.2.tgz", + "integrity": "sha1-6NV3TRwHOKR7z6hynzcS4t7d6yU=" } } } diff --git a/package.json b/package.json index ee7a1a5..af5f894 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git", "lodash": "^4.17.20", "moment": "^2.29.1", + "moment-duration-format": "^2.3.2", "multiselect-react-dropdown": "^1.6.2", "node-sass": "^4.14.1", "popper.js": "^1.16.1", @@ -57,6 +58,7 @@ "react-trafficlight": "^5.2.1", "sass": "^1.29.0", "superagent": "^6.1.0", + "swagger-ui-react": "^3.42.0", "ts-node": "^9.0.0", "type-fest": "^0.13.1", "typescript": "^4.1.2", diff --git a/src/app.js b/src/app.js index bcaced0..0a895b7 100644 --- a/src/app.js +++ b/src/app.js @@ -38,6 +38,7 @@ import Scenarios from './scenario/scenarios'; import Scenario from './scenario/scenario'; import Users from './user/users'; import User from './user/user'; +import APIBrowser from './common/api-browser'; import './styles/app.css'; @@ -117,6 +118,7 @@ class App extends React.Component { + diff --git a/src/common/api-browser.js b/src/common/api-browser.js new file mode 100644 index 0000000..b1e7375 --- /dev/null +++ b/src/common/api-browser.js @@ -0,0 +1,82 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import React from 'react'; + +import SwaggerUI from 'swagger-ui-react' +import 'swagger-ui-react/swagger-ui.css' +import '../styles/swagger-ui.css'; +import RestAPI from './api/rest-api'; +import RestDataManager from './data-managers/rest-data-manager'; + +class APIBrowser extends React.Component { + + constructor(props) { + super(props); + + this.state = { + 'spec': null + }; + } + + mangleSpec(spec) { + spec.host = window.location.host; + + return spec; + } + + componentWillMount() { + this._asyncRequest = RestAPI.get('/api/v2/openapi') + .then((spec) => { + this._asyncRequest = null; + + this.setState({ + 'spec': this.mangleSpec(spec) + }); + }); + } + + componentWillUnmount() { + if (this._asyncRequest) { + this._asyncRequest.cancel(); + } + } + + requestInterceptor(req) { + var token = localStorage.getItem('token'); + + if (token) + req.headers.Authorization = 'Bearer ' + token; + + return req + } + + render() { + return ( +
+ { this.state.spec && + } +
+ ); + } +} + +export default APIBrowser; diff --git a/src/common/api/rest-api.js b/src/common/api/rest-api.js index b33c36f..c35c0d3 100644 --- a/src/common/api/rest-api.js +++ b/src/common/api/rest-api.js @@ -26,10 +26,13 @@ import NotificationsFactory from "../data-managers/notifications-factory"; function isNetworkError(err, url) { let result = false; - // If not status nor response fields, it is a network error. TODO: Handle timeouts - if (err.status == null || err.status === 500 || err.response == null) { - result = true; + if (err.status === 500 && err.response != null){ + let notification = NotificationsFactory.INTERNAL_SERVER_ERROR(err.response) + NotificationsDataManager.addNotification(notification); + } else if (err.status == null || err.status === 500 || err.response == null) { + // If not status nor response fields, it is a network error. TODO: Handle timeouts + result = true; let notification = err.timeout? NotificationsFactory.REQUEST_TIMEOUT : NotificationsFactory.SERVER_NOT_REACHABLE(url); NotificationsDataManager.addNotification(notification); } diff --git a/src/common/data-managers/notifications-factory.js b/src/common/data-managers/notifications-factory.js index 7b4867b..ece1e8b 100644 --- a/src/common/data-managers/notifications-factory.js +++ b/src/common/data-managers/notifications-factory.js @@ -42,6 +42,14 @@ class NotificationsFactory { }; } + static INTERNAL_SERVER_ERROR(response) { + return { + title: 'Internal server error', + message: response.message, + level: 'error' + }; + } + static ADD_ERROR(message) { return { title: "Add Error", diff --git a/src/common/dialogs/delete-dialog.js b/src/common/dialogs/delete-dialog.js index 8d114f4..50d85a7 100644 --- a/src/common/dialogs/delete-dialog.js +++ b/src/common/dialogs/delete-dialog.js @@ -37,7 +37,7 @@ class DeleteDialog extends React.Component { Are you sure you want to delete the {this.props.title} '{this.props.name}'? - The IC will be deleted if the respective VILLAScontroller sends "gone" state and no component config is using the IC anymore + The IC will be deleted if the respective manager sends "gone" state and no component config is using the IC anymore diff --git a/src/common/home.js b/src/common/home.js index 053c293..0fc874d 100644 --- a/src/common/home.js +++ b/src/common/home.js @@ -18,28 +18,10 @@ import React from 'react'; import config from '../config'; -import {Redirect} from "react-router-dom"; +import { Redirect } from "react-router-dom"; +import { NavLink } from 'react-router-dom'; class Home extends React.Component { - constructor(props) { - super(props); - - // create url for API documentation, distinguish between localhost and production deployment - let docs_url = ""; - let docs_location = "/swagger/index.html"; - let base_url = window.location.origin; - if (base_url.search("localhost") === -1){ - docs_url = base_url + docs_location; - } else { - // useful for local testing, replace port 3000 with port 4000 (port of backend) - docs_url = base_url.replace("3000", "4000") + docs_location; - } - - this.state = { - docs_url: docs_url - }; - - } getCounts(type) { if (this.state.hasOwnProperty('counts')) @@ -68,10 +50,9 @@ class Home extends React.Component {

- An interactive documentation of the VILLASweb API is available here. + An interactive documentation of the VILLASweb API is available here.

-

Data Model

Datamodel VILLASweb @@ -103,8 +84,6 @@ class Home extends React.Component {
  • Users can have access to multiple scenarios
  • - -

    Credits

    VILLASweb is developed by the Institute for Automation of Complex Power Systems at the RWTH Aachen University.

      diff --git a/src/common/menu-sidebar.js b/src/common/menu-sidebar.js index 06d1342..2e953f8 100644 --- a/src/common/menu-sidebar.js +++ b/src/common/menu-sidebar.js @@ -33,6 +33,7 @@ class SidebarMenu extends React.Component { }
    • Account
    • Logout
    • +
    • API Browser
    ); diff --git a/src/common/table-column.js b/src/common/table-column.js index df68140..9d77606 100644 --- a/src/common/table-column.js +++ b/src/common/table-column.js @@ -35,6 +35,7 @@ class TableColumn extends Component { labelKey: null, checkbox: false, checkboxKey: '', + checkboxDisabled: null, labelStyle: null, labelModifier: null diff --git a/src/common/table.js b/src/common/table.js index 430b220..68f0dfe 100644 --- a/src/common/table.js +++ b/src/common/table.js @@ -59,7 +59,7 @@ class CustomTable extends Component { const modifier = child.props.modifier; if (modifier && content != null) { - content = modifier(content); + content = modifier(content, data); } let cell = []; @@ -72,7 +72,7 @@ class CustomTable extends Component { cell.push({content}); } else if (child.props.clickable) { cell.push(); - } else if (linkKey == 'filebuttons') { + } else if (linkKey === 'filebuttons') { content.forEach(element => { cell.push(Download {element}} > - - - ; + setAction = id => { + // search action + for (let action of this.props.actions) { + if (action.id === id) { + this.setState({ selectedAction: action }); + } } + }; + + setTimeForAction = (time) => { + this.setState({ time: new Date(time) }) + } + + render() { + + let sendCommandDisabled = this.props.runDisabled || this.state.selectedAction == null || this.state.selectedAction.id === "-1" + + let time = this.state.time.getFullYear().pad(4) + '-' + + this.state.time.getMonth().pad(2) + '-' + + this.state.time.getDay().pad(2) + 'T' + + this.state.time.getHours().pad(2) + ':' + + this.state.time.getMinutes().pad(2); + + const actionList = this.props.actions.map(action => ( + + {action.title} + + )); + + return
    + + + + {actionList} + + + + + + Select time for synced command execution +
    ; + } } export default ICAction; diff --git a/src/ic/ic-dialog.js b/src/ic/ic-dialog.js index 6e79fac..32a21d9 100644 --- a/src/ic/ic-dialog.js +++ b/src/ic/ic-dialog.js @@ -68,8 +68,28 @@ class ICDialog extends React.Component {
    -
    Status:
    +
    State: {this.props.ic.state}
    +
    Category: {this.props.ic.category}
    +
    Type: {this.props.ic.type}
    +
    Uptime: {this.props.ic.uptime}
    +
    Location: {this.props.ic.location}
    +
    Description: {this.props.ic.description}
    +
    Websocket URL: {this.props.ic.websocketurl}
    +
    API URL: {this.props.ic.apiurl}
    +
    Start parameter scheme:
    + + + + +
    Raw Status:
    - + {this.props.ic.type === "villas-node" || this.props.ic.type === "villas-relay" ? ( + <> +
    + +
    +
    Graph:
    +
    + {"Graph +
    - {this.props.ic.type === "villas-node" || this.props.ic.type === "villas-relay" ? ( - -
    - -
    -
    Graph:
    -
    - {"Graph -
    - - {this.props.userRole === "Admin" ? ( -
    -
    Controls:
    + {this.props.userRole === "Admin" ? (
    - - -
    -
    ) - : (
    )} +
    Controls:
    +
    + + +
    +
    ) + : (
    )} - this.closeConfirmModal(c)}/> - - ): (
    )} + this.closeConfirmModal(c)}/> + + ) : (
    )} + diff --git a/src/ic/ic-store.js b/src/ic/ic-store.js index d9ae16e..7cf314a 100644 --- a/src/ic/ic-store.js +++ b/src/ic/ic-store.js @@ -84,6 +84,7 @@ class InfrastructureComponentStore extends ArrayStore { let tempIC = action.ic; if(!tempIC.managedexternally){ tempIC.state = action.data.state; + tempIC.uptime = action.data.time_now - action.data.time_started; tempIC.statusupdateraw = action.data; AppDispatcher.dispatch({ type: 'ics/start-edit', diff --git a/src/ic/ics-data-manager.js b/src/ic/ics-data-manager.js index e4c9e44..02d480f 100644 --- a/src/ic/ics-data-manager.js +++ b/src/ic/ics-data-manager.js @@ -24,8 +24,14 @@ class IcsDataManager extends RestDataManager { super('ic', '/ic'); } - doActions(ic, action, token = null) { - RestAPI.post(this.makeURL(this.url + '/' + ic.id + '/action'), action, token).then(response => { + doActions(ic, actions, token = null) { + for (let action of actions) { + if (action.when) + // Send timestamp as Unix Timestamp + action.when = Math.round(action.when.getTime() / 1000); + } + + RestAPI.post(this.makeURL(this.url + '/' + ic.id + '/action'), actions, token).then(response => { AppDispatcher.dispatch({ type: 'ics/action-started', data: response diff --git a/src/ic/ics.js b/src/ic/ics.js index 1982b51..2347050 100644 --- a/src/ic/ics.js +++ b/src/ic/ics.js @@ -17,11 +17,12 @@ import React, { Component } from 'react'; import { Container } from 'flux/utils'; -import { Button, Badge } from 'react-bootstrap'; +import {Button, Badge, Tooltip, OverlayTrigger} from 'react-bootstrap'; import FileSaver from 'file-saver'; import _ from 'lodash'; import moment from 'moment' + import AppDispatcher from '../common/app-dispatcher'; import InfrastructureComponentStore from './ic-store'; @@ -74,9 +75,27 @@ class InfrastructureComponents extends Component { } }); + // collect number of external ICs + let externalICs = ics.filter(ic => ic.managedexternally === true) + let numberOfExternalICs = externalICs.length; + + // collect all IC categories + let managers = ics.filter(ic => ic.category === "manager") + let gateways = ics.filter(ic => ic.category === "gateway") + let simulators = ics.filter(ic => ic.category === "simulator") + let services = ics.filter(ic => ic.category === "service") + let equipment = ics.filter(ic => ic.category === "equipment") + + return { sessionToken: localStorage.getItem("token"), ics: ics, + managers: managers, + gateways: gateways, + simulators: simulators, + services: services, + equipment: equipment, + numberOfExternalICs, modalIC: {}, deleteModal: false, icModal: false, @@ -126,7 +145,6 @@ class InfrastructureComponents extends Component { } } - closeNewModal(data) { this.setState({ newModal : false }); @@ -195,7 +213,9 @@ class InfrastructureComponents extends Component { } } - onICChecked(index, event) { + onICChecked(ic, event) { + + let index = this.state.ics.indexOf(ic); const selectedICs = Object.assign([], this.state.selectedICs); for (let key in selectedICs) { if (selectedICs[key] === index) { @@ -220,8 +240,10 @@ class InfrastructureComponents extends Component { this.setState({ selectedICs: selectedICs }); } - runAction(action) { + runAction(action, when) { for (let index of this.state.selectedICs) { + action.when = when; + AppDispatcher.dispatch({ type: 'ics/start-action', ic: this.state.ics[index], @@ -241,7 +263,6 @@ class InfrastructureComponents extends Component { } stateLabelStyle(state, component){ - var style = [ 'badge' ]; if (InfrastructureComponents.isICOutdated(component) && state !== 'shutdown') { @@ -286,7 +307,6 @@ class InfrastructureComponents extends Component { default: style.push('badge-default'); - /* Possible states of ICs * 'error': ['resetting', 'error'], 'idle': ['resetting', 'error', 'idle', 'starting', 'shuttingdown'], @@ -305,50 +325,44 @@ class InfrastructureComponents extends Component { return style.join(' ') } - stateUpdateModifier(updatedAt) { + stateUpdateModifier(updatedAt, component) { let dateFormat = 'ddd, DD MMM YYYY HH:mm:ss ZZ'; let dateTime = moment(updatedAt, dateFormat); return dateTime.fromNow() } - modifyManagedExternallyColumn(managedExternally){ - + modifyManagedExternallyColumn(managedExternally, component){ if(managedExternally){ return } else { return "" } - } - modifyUptimeColumn(uptime){ + modifyUptimeColumn(uptime, component){ if(uptime >= 0){ - return {uptime + "s"} + let momentDurationFormatSetup = require("moment-duration-format"); + momentDurationFormatSetup(moment) + + let timeString = moment.duration(uptime, "seconds").humanize(); + return {timeString} } else{ return Unknown } } - modifyNameColumn(name){ - let ic = this.state.ics.find(ic => ic.name === name); - - if(ic.type === "villas-node" || ic.type === "villas-relay" || ic.managedexternally){ - return } - else{ - return {name} - } + modifyNameColumn(name, component){ + let index = this.state.ics.indexOf(component); + return } openICStatus(ic){ - let index = this.state.ics.indexOf(ic); - this.setState({ icModal: true, modalIC: ic, modalIndex: index }) } sendControlCommand(command,ic){ - if(command === "restart"){ AppDispatcher.dispatch({ type: 'ics/restart', @@ -362,79 +376,140 @@ class InfrastructureComponents extends Component { token: this.state.sessionToken, }); } + } + + isExternalIC(index){ + let ic = this.state.ics[index] + return ic.managedexternally + } + + getICCategoryTable(ics, editable, title){ + if (ics && ics.length > 0) { + return (
    +

    {title}

    + + this.isExternalIC(index)} + onChecked={(ic, event) => this.onICChecked(ic, event)} + width='30' + /> + this.modifyNameColumn(name, component)} + /> + this.stateLabelStyle(state, component)} + /> + + this.modifyUptimeColumn(uptime, component)} + /> + this.stateUpdateModifier(stateUpdateAt, component)} + /> + + {this.state.currentUser.role === "Admin" && editable ? + this.setState({editModal: true, modalIC: ics[index], modalIndex: index})} + onExport={index => this.exportIC(index)} + onDelete={index => this.setState({deleteModal: true, modalIC: ics[index], modalIndex: index})} + /> + : + this.exportIC(index)} + /> + } +
    +
    ); + } else { + return
    + } } render() { + const buttonStyle = { marginLeft: '10px' }; + let managerTable = this.getICCategoryTable(this.state.managers, false, "IC Managers") + let simulatorTable = this.getICCategoryTable(this.state.simulators, true, "Simulators") + let gatewayTable = this.getICCategoryTable(this.state.gateways, true, "Gateways") + let serviceTable = this.getICCategoryTable(this.state.services, true, "Services") + let equipmentTable = this.getICCategoryTable(this.state.equipment, true, "Equipment") + return (
    -

    Infrastructure Components

    - - - this.onICChecked(index, event)} width='30' /> - this.modifyNameColumn(name)}/> - this.stateLabelStyle(state, component)} /> - - - this.modifyManagedExternallyColumn(managedexternally)} width='105' /> - this.modifyUptimeColumn(uptime)}/> - - {/* */} - - - this.stateUpdateModifier(stateUpdateAt)} /> +

    Infrastructure Components {this.state.currentUser.role === "Admin" ? - this.setState({ editModal: true, modalIC: this.state.ics[index], modalIndex: index })} - onExport={index => this.exportIC(index)} - onDelete={index => this.setState({ deleteModal: true, modalIC: this.state.ics[index], modalIndex: index })} - /> - : - this.exportIC(index)} - /> + ( + Add Infrastructure Component } > + + + Import Infrastructure Component } > + + + ) + : + ( ) } -

    - {this.state.currentUser.role === "Admin" ? -
    + + + {managerTable} + {simulatorTable} + {gatewayTable} + {serviceTable} + {equipmentTable} + + {this.state.currentUser.role === "Admin" && this.state.numberOfExternalICs > 0 ? +
    this.runAction(action)} + runAction={(action, when) => this.runAction(action, when)} actions={[ - { id: '-1', title: 'Select command', data: { action: 'none' } }, - { id: '0', title: 'Reset', data: { action: 'reset' } }, - { id: '1', title: 'Shutdown', data: { action: 'shutdown' } }, - ]} + {id: '-1', title: 'Action', data: {action: 'none'}}, + {id: '0', title: 'Reset', data: {action: 'reset'}}, + {id: '1', title: 'Shutdown', data: {action: 'shutdown'}}, + ]} />
    : -
    - } - - {this.state.currentUser.role === "Admin" ? -
    - - -
    - : -
    +
    }
    - this.closeNewModal(data)} /> + this.closeNewModal(data)} managers={this.state.managers} /> this.closeEditModal(data)} ic={this.state.modalIC} /> this.closeImportModal(data)} /> + this.closeDeleteModal(e)} /> this.closeICModal(data)} @@ -443,7 +518,6 @@ class InfrastructureComponents extends Component { userRole={this.state.currentUser.role} sendControlCommand={(command, ic) => this.sendControlCommand(command, ic)}/> - this.closeDeleteModal(e)} />
    ); } diff --git a/src/ic/new-ic.js b/src/ic/new-ic.js index 4f52667..b95b543 100644 --- a/src/ic/new-ic.js +++ b/src/ic/new-ic.js @@ -34,7 +34,8 @@ class NewICDialog extends React.Component { category: '', managedexternally: false, description: '', - location: '' + location: '', + manager: '' }; } @@ -48,7 +49,8 @@ class NewICDialog extends React.Component { uuid: this.state.uuid, managedexternally: this.state.managedexternally, location: this.state.location, - description: this.state.description + description: this.state.description, + manager: this.state.manager }; if (this.state.websocketurl != null && this.state.websocketurl !== "" && this.state.websocketurl !== 'http://') { @@ -88,6 +90,7 @@ class NewICDialog extends React.Component { let websocketurl = true; let type = true; let category = true; + let manager = true; if (this.state.name === '') { name = false; @@ -97,6 +100,10 @@ class NewICDialog extends React.Component { uuid = false; } + if(this.state.managedexternally && manager === ''){ + manager = false; + } + if (this.state.type === '') { type = false; } @@ -105,7 +112,7 @@ class NewICDialog extends React.Component { category = false; } - this.valid = name && uuid && websocketurl && type && category; + this.valid = name && uuid && websocketurl && type && category && manager; // return state to control if (target === 'name') return name ? "success" : "error"; @@ -113,6 +120,7 @@ class NewICDialog extends React.Component { if (target === 'websocketurl') return websocketurl ? "success" : "error"; if (target === 'type') return type ? "success" : "error"; if (target === 'category') return category ? "success" : "error"; + if (target === 'manager') return manager ? "success" : "error"; return this.valid; } @@ -131,8 +139,8 @@ class NewICDialog extends React.Component { case "simulator": typeOptions = ["dummy","generic","dpsim","rtlab","rscad","opalrt"]; break; - case "controller": - typeOptions = ["kubernetes","villas-controller"]; + case "manager": + typeOptions = ["villas-node","villas-relay","generic"]; break; case "gateway": typeOptions = ["villas-node","villas-relay"]; @@ -149,30 +157,49 @@ class NewICDialog extends React.Component { return ( this.onClose(c)} onReset={() => this.resetState()} valid={this.validateForm()}>
    - - An externally managed component will show up in the list only after a VILLAScontroller for the component type has created the component and cannot be edited by users} > - this.handleChange(e)}> - - - + {this.props.managers.length > 0 ? + <> + + An externally managed component is created and managed by an IC manager via AMQP} > + this.handleChange(e)}> + + + + {this.state.managedexternally === true ? + + Required field } > + Manager to create new IC * + + this.handleChange(e)}> + {this.props.managers.map((m) => ( + + ))} + + + :
    + + } + + :
    + } - Required field } > + Required field } > Name * this.handleChange(e)} /> - Required field } > + Required field } > Category of component * this.handleChange(e)}> - + @@ -206,11 +233,15 @@ class NewICDialog extends React.Component { this.handleChange(e)} /> - - UUID - this.handleChange(e)} /> - - + {this.state.managedexternally === false ? + + UUID + this.handleChange(e)}/> + + + :
    + }
    ); diff --git a/src/result/edit-result.js b/src/result/edit-result.js index 4833251..e1992c1 100644 --- a/src/result/edit-result.js +++ b/src/result/edit-result.js @@ -16,7 +16,7 @@ ******************************************************************************/ import React from 'react'; -import {FormGroup, FormControl, FormLabel, Col, Button, ProgressBar} from 'react-bootstrap'; +import { FormGroup, FormControl, FormLabel, Col, Row, Button, ProgressBar } from 'react-bootstrap'; import AppDispatcher from "../common/app-dispatcher"; import FileStore from "../file/file-store" @@ -38,8 +38,6 @@ class EditResultDialog extends React.Component { uploadFile: null, uploadProgress: 0, files: null, - result: null, - resultExists: false, }; } @@ -53,26 +51,38 @@ class EditResultDialog extends React.Component { this.setState({ [event.target.id]: event.target.value }); }; + isEmpty(val) { + return (val === undefined || val == null || val.length <= 0); + }; + componentDidUpdate(prevProps, prevState) { - if (this.state.resultExists && this.props.files != prevProps.files) { - this.setState({files: FileStore.getState().filter(file => this.state.result.resultFileIDs.includes(file.id))}); - } - if (this.props.result != prevProps.result && Object.keys(this.props.result).length != 0) { - this.setState({ - id: this.props.result.id, - description: this.props.result.description, - result: this.props.result, - resultExists: true, - files: FileStore.getState().filter(file => this.props.result.resultFileIDs.includes(file.id)), - }) - } - } + if (this.props.resultId != prevProps.resultId || this.props.results != prevProps.results) { + let result = this.props.results[this.props.resultId]; + + if (result && Object.keys(result).length != 0) { + let hasFiles = !this.isEmpty(result.resultFileIDs); + if (hasFiles) { + this.setState({ + id: result.id, + description: result.description, + files: FileStore.getState().filter(file => result.resultFileIDs.includes(file.id)), + }) + } else { + this.setState({ + id: result.id, + description: result.description, + files: null, + }) + } + } + } + }; selectUploadFile(event) { this.setState({ uploadFile: event.target.files[0] }); }; - startFileUpload(){ + startFileUpload() { const formData = new FormData(); formData.append("file", this.state.uploadFile); @@ -97,68 +107,98 @@ class EditResultDialog extends React.Component { this.setState({ uploadProgress: parseInt(event.percent.toFixed(), 10) }); }; - deleteFile(index){ + deleteFile(index) { let file = this.state.files[index]; AppDispatcher.dispatch({ - type: 'files/start-remove', - data: file, + type: 'resultfiles/start-remove', + resultID: this.state.id, + fileID: file.id, token: this.props.sessionToken }); + + } + + submitDescription() { + let result = this.props.results[this.props.resultId]; + if (!this.isEmpty(result)) { + result.description = this.state.description; + AppDispatcher.dispatch({ + type: 'results/start-edit', + data: result, + token: this.props.sessionToken + }); + } } render() { return this.onClose()} - blendOutCancel = {true} - valid={true} - size = 'lg'> + title={'Edit Result No. ' + this.state.id} + buttonTitle='Close' + onClose={() => this.onClose()} + blendOutCancel={true} + valid={true} + size='lg'>
    - Description - - + + + + Description + + + + + + + + + + + - - - - - this.deleteFile(index)} - /> + + + + + this.deleteFile(index)} + />
    - Add Result File - this.selectUploadFile(event)} /> + Add Result File + this.selectUploadFile(event)} /> - - - + + + + + - - - -
    ; } diff --git a/src/result/result-store.js b/src/result/result-store.js index d824b1e..519c94a 100644 --- a/src/result/result-store.js +++ b/src/result/result-store.js @@ -15,40 +15,14 @@ * along with VILLASweb. If not, see . ******************************************************************************/ - import ArrayStore from '../common/array-store'; import ResultsDataManager from './results-data-manager'; -import FilesDataManager from '../file/files-data-manager' class ResultStore extends ArrayStore { constructor() { super('results', ResultsDataManager); } - saveFile(state, action){ - - let fileID = parseInt(action.id) - state.forEach((element, index, array) => { - if (element.id === fileID) { - // save blob object - array[index]["data"] = new Blob([action.data.data], {type: action.data.type}); - // update file type - array[index]["type"] = action.data.type; - - if (array[index]["objectURL"] !== ''){ - // free memory of previously generated object URL - URL.revokeObjectURL(array[index]["objectURL"]); - } - // create an object URL for the file - array[index]["objectURL"] = URL.createObjectURL(array[index]["data"]) - } - }); - - // announce change to listeners - this.__emitChange(); - return state - } - simplify(timestamp) { let parts = timestamp.split("T"); let datestr = parts[0]; @@ -67,17 +41,29 @@ class ResultStore extends ArrayStore { reduce(state, action) { switch (action.type) { case 'results/loaded': - this.simplifyTimestamps(action.data); + if (Array.isArray(action.data)) { + this.simplifyTimestamps(action.data); + } else { + this.simplifyTimestamps([action.data]); + } return super.reduce(state, action); case 'results/added': this.simplifyTimestamps([action.data]); return super.reduce(state, action); + case 'results/edited': + this.simplifyTimestamps([action.data]); + return super.reduce(state, action); + case 'resultfiles/start-upload': ResultsDataManager.uploadFile(action.data, action.resultID, action.token, action.progressCallback, action.finishedCallback, action.scenarioID); return state; + case 'resultfiles/start-remove': + ResultsDataManager.removeFile(action.resultID, action.fileID, action.token); + return state; + default: return super.reduce(state, action); } diff --git a/src/result/results-data-manager.js b/src/result/results-data-manager.js index 5c7e2e0..3584c67 100644 --- a/src/result/results-data-manager.js +++ b/src/result/results-data-manager.js @@ -19,24 +19,24 @@ import RestDataManager from '../common/data-managers/rest-data-manager'; import RestAPI from '../common/api/rest-api'; import AppDispatcher from '../common/app-dispatcher'; -class ResultsDataManager extends RestDataManager{ +class ResultsDataManager extends RestDataManager { constructor() { super('result', '/results'); } uploadFile(file, resultID, token = null, progressCallback = null, finishedCallback = null, scenarioID) { - RestAPI.upload(this.makeURL(this.url + '/' + resultID + '/file') , file, token, progressCallback, scenarioID).then(response => { + RestAPI.upload(this.makeURL(this.url + '/' + resultID + '/file'), file, token, progressCallback, scenarioID).then(response => { AppDispatcher.dispatch({ type: 'files/uploaded', }); - // Trigger a results reload + // Trigger a result reload AppDispatcher.dispatch({ type: 'results/start-load', - param: '?scenarioID=' + scenarioID, - token: token + data: resultID, + token: token, }); // Trigger a files reload @@ -57,6 +57,23 @@ class ResultsDataManager extends RestDataManager{ }); } + removeFile(resultID, fileID, token) { + RestAPI.delete(this.makeURL(this.url + '/' + resultID + '/file/' + fileID), token).then(response => { + // reload result + AppDispatcher.dispatch({ + type: 'results/start-load', + data: resultID, + token: token, + }); + + // update files + AppDispatcher.dispatch({ + type: 'files/removed', + data: fileID, + token: token, + }); + }); + } } export default new ResultsDataManager(); \ No newline at end of file diff --git a/src/scenario/scenario.js b/src/scenario/scenario.js index 56bdd96..7bf2de9 100644 --- a/src/scenario/scenario.js +++ b/src/scenario/scenario.js @@ -103,6 +103,7 @@ class Scenario extends React.Component { files: FileStore.getState().filter(file => file.scenarioID === parseInt(props.match.params.scenario, 10)), ics: ICStore.getState(), + ExternalICInUse: false, deleteConfigModal: false, importConfigModal: false, @@ -113,9 +114,11 @@ class Scenario extends React.Component { editResultsModal: prevState.editResultsModal || false, modalResultsData: {}, - modalResultsIndex: 0, + modalResultsIndex: prevState.modalResultsIndex, newResultModal: false, - filesToDownload: [], + filesToDownload: prevState.filesToDownload, + zipfiles: prevState.zipfiles || false, + resultNodl: prevState.resultNodl, editOutputSignalsModal: prevState.editOutputSignalsModal || false, editInputSignalsModal: prevState.editInputSignalsModal || false, @@ -158,25 +161,28 @@ class Scenario extends React.Component { componentDidUpdate(prevProps, prevState) { // check whether file data has been loaded - if (this.state.filesToDownload.length > 0 ) { - if (this.state.filesToDownload.length === 1) { - let fileToDownload = FileStore.getState().filter(file => file.id === this.state.filesToDownload[0]) - if (fileToDownload.length === 1 && fileToDownload[0].data) { - const blob = new Blob([fileToDownload[0].data], {type: fileToDownload[0].type}); - FileSaver.saveAs(blob, fileToDownload[0].name); - this.setState({ filesToDownload: [] }); - } - } else { // zip and save several files - let filesToDownload = FileStore.getState().filter(file => this.state.filesToDownload.includes(file.id) && file.data); - if (filesToDownload.length === this.state.filesToDownload.length) { // all requested files have been loaded - var zip = new JSZip(); - filesToDownload.forEach(file => { - zip.file(file.name, file.data); - }); - zip.generateAsync({type: "blob"}).then(function(content) { - saveAs(content, "results.zip"); - }); - this.setState({ filesToDownload: [] }); + if (this.state.filesToDownload && this.state.filesToDownload.length > 0 ) { + if (this.state.files != prevState.files) { + if (!this.state.zipfiles) { + let fileToDownload = FileStore.getState().filter(file => file.id === this.state.filesToDownload[0]) + if (fileToDownload.length === 1 && fileToDownload[0].data) { + const blob = new Blob([fileToDownload[0].data], {type: fileToDownload[0].type}); + FileSaver.saveAs(blob, fileToDownload[0].name); + this.setState({ filesToDownload: [] }); + } + } else { // zip and save one or more files (download all button) + let filesToDownload = FileStore.getState().filter(file => this.state.filesToDownload.includes(file.id) && file.data); + if (filesToDownload.length === this.state.filesToDownload.length) { // all requested files have been loaded + var zip = new JSZip(); + filesToDownload.forEach(file => { + zip.file(file.name, file.data); + }); + let zipname = "result_" + this.state.resultNodl + "_" + Date.now(); + zip.generateAsync({type: "blob"}).then(function(content) { + saveAs(content, zipname); + }); + this.setState({ filesToDownload: [] }); + } } } } @@ -361,9 +367,30 @@ class Scenario extends React.Component { this.setState({ selectedConfigs: selectedConfigs }); } - runAction(action, delay) { - // delay in seconds + usesExternalIC(index){ + let icID = this.state.configs[index].icID; + let ic = null; + for (let component of this.state.ics) { + if (component.id === this.state.configs[index].icID) { + ic = component; + } + } + + if (ic == null) { + return false; + } + + if (ic.managedexternally === true){ + this.setState({ExternalICInUse: true}) + return true + } + + return false + + } + + runAction(action, when) { if (action.data.action === 'none') { console.warn("No command selected. Nothing was sent."); return; @@ -386,8 +413,7 @@ class Scenario extends React.Component { action.data.parameters = this.state.configs[index].startParameters; } - // Unix time stamp + delay - action.data.when = Math.round(Date.now() / 1000.0 + delay) + action.data.when = when; console.log("Sending action: ", action.data) @@ -613,21 +639,19 @@ class Scenario extends React.Component { closeEditResultsModal() { this.setState({ editResultsModal: false }); - - AppDispatcher.dispatch({ - type: 'results/start-load', - token: this.state.sessionToken, - param: '?scenarioID=' + this.state.scenario.id - }) } downloadResultData(param) { let toDownload = []; + let zip = false; - if (typeof(param) === 'object') { + if (typeof(param) === 'object') { // download all files toDownload = param.resultFileIDs; - } else { + zip = true; + this.setState({ filesToDownload: toDownload, zipfiles: zip, resultNodl: param.id }); + } else { // download one file toDownload.push(param); + this.setState({ filesToDownload: toDownload, zipfiles: zip}); } toDownload.forEach(fileid => { @@ -637,8 +661,6 @@ class Scenario extends React.Component { token: this.state.sessionToken }); }); - - this.setState({ filesToDownload: toDownload }); } closeDeleteResultsModal(confirmDelete) { @@ -736,7 +758,7 @@ class Scenario extends React.Component { editButton downloadAllButton deleteButton - onEdit={index => this.setState({ editResultsModal: true, modalResultsData: this.state.results[index], modalResultsIndex: index })} + onEdit={index => this.setState({ editResultsModal: true, modalResultsIndex: index })} onDownloadAll={(index) => this.downloadResultData(this.state.results[index])} onDelete={(index) => this.setState({ deleteResultsModal: true, modalResultsData: this.state.results[index], modalResultsIndex: index })} /> @@ -746,7 +768,8 @@ class Scenario extends React.Component { sessionToken={this.state.sessionToken} show={this.state.editResultsModal} files={this.state.files} - result={this.state.modalResultsData} + results={this.state.results} + resultId={this.state.modalResultsIndex} scenarioID={this.state.scenario.id} onClose={this.closeEditResultsModal.bind(this)} /> this.closeDeleteResultsModal(e)} /> @@ -772,12 +795,27 @@ class Scenario extends React.Component { scenarioID={this.state.scenario.id} /> - - {/*Component Configurations table*/} -

    Component Configurations

    +

    Component Configurations + Add Component Configuration } > + + + Import Component Configuration } > + + +

    - this.onConfigChecked(index, event)} width='30' /> + this.usesExternalIC(index)} + onChecked={(index, event) => this.onConfigChecked(index, event)} + width='30' /> this.getListOfFiles(fileIDs, ['json', 'JSON'])} />
    -
    - this.runAction(action, delay)} - actions={[ - { id: '-1', title: 'Select command', data: { action: 'none' } }, - { id: '0', title: 'Start', data: { action: 'start' } }, - { id: '1', title: 'Stop', data: { action: 'stop' } }, - { id: '2', title: 'Pause', data: { action: 'pause' } }, - { id: '3', title: 'Resume', data: { action: 'resume' } } - ]} /> -
    - -
    - - -
    - + { this.state.ExternalICInUse ? ( +
    + this.runAction(action, when)} + actions={[ + {id: '-1', title: 'Action', data: {action: 'none'}}, + {id: '0', title: 'Start', data: {action: 'start'}}, + {id: '1', title: 'Stop', data: {action: 'stop'}}, + {id: '2', title: 'Pause', data: {action: 'pause'}}, + {id: '3', title: 'Resume', data: {action: 'resume'}} + ]}/> +
    + ) : (
    ) + }
    {/*Dashboard table*/} -

    Dashboards

    +

    Dashboards + Add Dashboard } > + + + Import Dashboard } > + + +

    @@ -887,13 +935,6 @@ class Scenario extends React.Component { />
    -
    - - -
    - -
    - this.closeNewDashboardModal(data)} /> this.closeEditDashboardModal(data)} /> this.closeImportDashboardModal(data)} /> @@ -901,18 +942,15 @@ class Scenario extends React.Component { this.closeDeleteDashboardModal(e)} /> {/*Result table*/} -
    -

    Results - -

    - -
    +

    Results + Add Result } > + + +

    {resulttable} - {/* -
    - -
    - */} this.closeNewResultModal(data)} /> {/*Scenario Users table*/} diff --git a/src/scenario/scenarios.js b/src/scenario/scenarios.js index d176d0f..8158af8 100644 --- a/src/scenario/scenarios.js +++ b/src/scenario/scenarios.js @@ -17,7 +17,7 @@ import React, { Component } from 'react'; import { Container } from 'flux/utils'; -import { Button } from 'react-bootstrap'; +import {Button, OverlayTrigger, Tooltip} from 'react-bootstrap'; import FileSaver from 'file-saver'; import AppDispatcher from '../common/app-dispatcher'; @@ -241,7 +241,20 @@ class Scenarios extends Component { return (
    -

    Scenarios

    +

    Scenarios + Add Scenario } > + + + Import Scenario } > + + +

    @@ -260,13 +273,6 @@ class Scenarios extends Component { />
    -
    - - -
    - -
    - this.closeNewModal(data)} /> this.closeEditModal(data)} scenario={this.state.modalScenario} /> this.closeImportModal(data)} nodes={this.state.nodes} /> diff --git a/src/styles/swagger-ui.css b/src/styles/swagger-ui.css new file mode 100644 index 0000000..066c2e1 --- /dev/null +++ b/src/styles/swagger-ui.css @@ -0,0 +1,3 @@ +.swagger-ui div.scheme-container { + display: none +} diff --git a/src/styles/widgets.css b/src/styles/widgets.css index 06032c2..6f91c0e 100644 --- a/src/styles/widgets.css +++ b/src/styles/widgets.css @@ -398,6 +398,8 @@ div[class*="-widget"] label { /* Begin time offset widget */ .time-offset { + width: 100%; + height: 100%; display: flex; align-items: center; justify-content: space-around; diff --git a/src/user/login-form.js b/src/user/login-form.js index 1c2fc5e..3c26f51 100644 --- a/src/user/login-form.js +++ b/src/user/login-form.js @@ -70,14 +70,14 @@ class LoginForm extends Component { Username - this.handleChange(e)} /> + this.handleChange(e)} /> Password - this.handleChange(e)} /> + this.handleChange(e)} /> @@ -95,11 +95,11 @@ class LoginForm extends Component { - + this.closeRecoverPassword()} sessionToken={this.props.sessionToken} /> - + ); } } diff --git a/src/user/recover-password.js b/src/user/recover-password.js index 556f0d2..2602d9c 100644 --- a/src/user/recover-password.js +++ b/src/user/recover-password.js @@ -17,7 +17,7 @@ import React from 'react'; import Dialog from '../common/dialogs/dialog'; -import Config from '../config.js'; +import Config from '../config'; class RecoverPassword extends React.Component { @@ -29,13 +29,10 @@ class RecoverPassword extends React.Component { } } - onClose() { this.props.onClose(); } - - render() { return ( this.onClose(c)} blendOutCancel = {true} valid={true} size = 'lg'> @@ -43,7 +40,7 @@ class RecoverPassword extends React.Component {
    Please contact:
    {this.state.admin.name}
    E-Mail:
    - {this.state.admin.mail} + {this.state.admin.mail}
    ); diff --git a/src/user/users.js b/src/user/users.js index af4fbaa..d577462 100644 --- a/src/user/users.js +++ b/src/user/users.js @@ -17,7 +17,7 @@ import React, { Component } from 'react'; import { Container } from 'flux/utils'; -import { Button } from 'react-bootstrap'; +import {Button, OverlayTrigger, Tooltip} from 'react-bootstrap'; import AppDispatcher from '../common/app-dispatcher'; import UsersStore from './users-store'; @@ -130,9 +130,22 @@ class Users extends Component { render() { + const buttonStyle = { + marginLeft: '10px' + }; + return (
    -

    Users

    +

    Users + + Add User } > + + + +

    @@ -143,7 +156,7 @@ class Users extends Component { this.setState({ editModal: true, modalData: this.state.users[index] })} onDelete={index => this.setState({ deleteModal: true, modalData: this.state.users[index] })} />
    - + this.closeNewModal(data)} /> this.closeEditModal(data)} user={this.state.modalData} /> diff --git a/src/widget/edit-widget/edit-widget-control-creator.js b/src/widget/edit-widget/edit-widget-control-creator.js index 5617d26..d2ee3fc 100644 --- a/src/widget/edit-widget/edit-widget-control-creator.js +++ b/src/widget/edit-widget/edit-widget-control-creator.js @@ -170,7 +170,8 @@ export default function CreateControls(widgetType = null, widget = null, session handleChange(e)} />, handleChange(e)} />, handleChange(e)} />, - handleChange(e)} />, + handleChange(e)} />, + handleChange(e)} />, ); break; diff --git a/src/widget/edit-widget/edit-widget-plot-colors-control.js b/src/widget/edit-widget/edit-widget-plot-colors-control.js index 567e84f..a612277 100644 --- a/src/widget/edit-widget/edit-widget-plot-colors-control.js +++ b/src/widget/edit-widget/edit-widget-plot-colors-control.js @@ -19,6 +19,8 @@ import React, { Component } from 'react'; import { FormGroup, OverlayTrigger, Tooltip , FormLabel, Button } from 'react-bootstrap'; import ColorPicker from './color-picker' import Icon from "../../common/icon"; +import {scaleOrdinal} from "d3-scale"; +import {schemeCategory10} from "d3-scale-chromatic"; // schemeCategory20 no longer available in d3 @@ -36,8 +38,20 @@ class EditWidgetPlotColorsControl extends Component { } static getDerivedStateFromProps(props, state){ + + let widget = props.widget; + if(widget.customProperties.lineColors === undefined || widget.customProperties.lineColors === null){ + // for backwards compatibility with old plots + widget.customProperties.lineColors = [] + + const newLineColor = scaleOrdinal(schemeCategory10); + for (let signalID of widget.signalIDs){ + widget.customProperties.lineColors.push(newLineColor(signalID)) + } + } + return { - widget: props.widget + widget: widget }; } @@ -62,9 +76,9 @@ class EditWidgetPlotColorsControl extends Component { this.setState({selectedIndex: null}); } } - + render() { - + return ( Line Colors @@ -81,7 +95,7 @@ class EditWidgetPlotColorsControl extends Component { } let signal = this.props.signals.find(signal => signal.id === signalID); - + return ({signal.name}}> diff --git a/src/widget/widget-factory.js b/src/widget/widget-factory.js index 4bb4aaf..ed04630 100644 --- a/src/widget/widget-factory.js +++ b/src/widget/widget-factory.js @@ -168,6 +168,7 @@ class WidgetFactory { widget.customProperties.valueMin = 0; widget.customProperties.valueMax = 1; widget.customProperties.valueUseMinMax = false; + widget.customProperties.lockAspect = true; break; case 'Box': widget.minWidth = 50; @@ -209,6 +210,7 @@ class WidgetFactory { widget.customProperties.horizontal = true; widget.customProperties.showOffset = true; widget.customProperties.lockAspect = true; + widget.customProperties.showName = true; break; default: diff --git a/src/widget/widget-plot/plot-legend.js b/src/widget/widget-plot/plot-legend.js index 1ada30d..4dd7459 100644 --- a/src/widget/widget-plot/plot-legend.js +++ b/src/widget/widget-plot/plot-legend.js @@ -20,7 +20,7 @@ import { scaleOrdinal} from 'd3-scale'; import {schemeCategory10} from 'd3-scale-chromatic' function Legend(props){ - + const signal = props.sig; const hasScalingFactor = (signal.scalingFactor !== 1); @@ -52,10 +52,15 @@ class PlotLegend extends React.Component { return
      - { + { this.props.lineColors !== undefined && this.props.lineColors != null ? ( this.props.signals.map( signal => - )} + )) : ( + this.props.signals.map( signal => + + )) + } +
    ; } diff --git a/src/widget/widget-plot/plot.js b/src/widget/widget-plot/plot.js index 1677915..6f314fe 100644 --- a/src/widget/widget-plot/plot.js +++ b/src/widget/widget-plot/plot.js @@ -207,6 +207,11 @@ class Plot extends React.Component { const lines = this.state.data.map((values, index) => { let signalID = this.props.signalIDs[index]; + + if(this.props.lineColors === undefined || this.props.lineColors === null){ + this.props.lineColors = [] // for backwards compatibility + } + if (typeof this.props.lineColors[signalID] === "undefined") { this.props.lineColors[signalID] = newLineColor(signalID); } diff --git a/src/widget/widgets/time-offset.js b/src/widget/widgets/time-offset.js index 6c6569b..3682da8 100644 --- a/src/widget/widgets/time-offset.js +++ b/src/widget/widgets/time-offset.js @@ -71,7 +71,10 @@ class WidgetTimeOffset extends Component {
    {this.props.widget.customProperties.icID !== -1 ? () : (no IC) - } + } + {this.props.widget.customProperties.icID !== -1 && this.props.widget.customProperties.showName ? + ({this.state.icName}) : () + } {this.props.widget.customProperties.icID !== -1 ? ({this.state.icName}

    Offset: {this.state.timeOffset + "s"}
    )