diff --git a/web/package-lock.json b/web/package-lock.json index 506b022..c9a552e 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -26,6 +26,7 @@ "react": "^17.0.2", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^17.0.2", + "react-highlight": "^0.15.0", "react-monaco-editor": "^0.48.0", "react-router-dom": "^6.3.0", "react-toastify": "^9.0.1" @@ -42,6 +43,7 @@ "@types/react": "^17.0.37", "@types/react-copy-to-clipboard": "^5.0.2", "@types/react-dom": "^17.0.11", + "@types/react-highlight": "^0.12.5", "@types/react-router-dom": "^5.3.1", "babel-loader": "^8.2.3", "babel-plugin-prismjs": "^2.1.0", @@ -60,11 +62,18 @@ "style-loader": "^3.3.1", "ts-loader": "^9.2.6", "typescript": "^4.5.2", + "typescript-plugin-css-modules": "^5.0.1", "webpack": "5.64.3", "webpack-cli": "^4.9.2", "webpack-dev-server": "4.7.4" } }, + "node_modules/@adobe/css-tools": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", + "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==", + "dev": true + }, "node_modules/@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", @@ -3322,6 +3331,24 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "node_modules/@types/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-0VLab/pcLTLcfbxi6THSIMVYcw9hEUBGvjwwaGpW77mMgRXfGF+a76t7BxTGyLh1y68tBvrffp8UWnqvm76+yg==", + "dev": true, + "dependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/@types/postcss-modules-scope": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/postcss-modules-scope/-/postcss-modules-scope-3.0.1.tgz", + "integrity": "sha512-LNkp3c4ML9EQj2dgslp4i80Jxj72YK3HjYzrTn6ftUVylW1zaKFGqrMlNIyqBmPWmIhZ/Y5r0Y4T49Hk1IuDUg==", + "dev": true, + "dependencies": { + "postcss": "^8.0.0" + } + }, "node_modules/@types/prismjs": { "version": "1.16.6", "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.16.6.tgz", @@ -3373,6 +3400,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-highlight": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/react-highlight/-/react-highlight-0.12.5.tgz", + "integrity": "sha512-P8+mTxltxDdQ+99l+pjn40clziSbNrZy5d5zmvG+j3jKzokAhCoCZlIRmmnFgETTYubuqwKjvXSlvesBZcTfvQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-is": { "version": "17.0.3", "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", @@ -4961,6 +4997,18 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/copy-to-clipboard": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", @@ -5226,80 +5274,6 @@ "webpack": "^5.0.0" } }, - "node_modules/css-loader/node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/css-loader/node_modules/postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", - "dev": true, - "dependencies": { - "nanoid": "^3.2.0", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/css-loader/node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/css-loader/node_modules/postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/css-loader/node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/css-loader/node_modules/postcss-modules-values": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", @@ -5330,15 +5304,6 @@ "node": ">=10" } }, - "node_modules/css-loader/node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/css-select": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.2.1.tgz", @@ -5728,6 +5693,15 @@ "tslib": "^2.0.3" } }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/dotenv-defaults": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", @@ -5828,6 +5802,19 @@ "node": ">=4" } }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -7253,6 +7240,14 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, "node_modules/history": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", @@ -7483,6 +7478,18 @@ "node": ">=0.10.0" } }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/ignore": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", @@ -7492,6 +7499,19 @@ "node": ">= 4" } }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/immutable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", @@ -7896,6 +7916,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -8084,12 +8110,9 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dependencies": { - "minimist": "^1.2.5" - }, + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "bin": { "json5": "lib/cli.js" }, @@ -8225,6 +8248,42 @@ "language-subtag-registry": "~0.3.2" } }, + "node_modules/less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less/node_modules/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, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -8238,6 +8297,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -8281,6 +8349,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -8487,7 +8561,8 @@ "node_modules/minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "node_modules/mkdirp": { "version": "0.5.5", @@ -8543,10 +8618,16 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.0.tgz", - "integrity": "sha512-JzxqqT5u/x+/KOFSd7JP15DOo9nOoHpx6DYatqIHUW2+flybkm+mdcraotSQR5WcnZr+qhGVh8Ted0KdfSMxlg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -8560,6 +8641,47 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -8958,6 +9080,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", @@ -9152,6 +9283,107 @@ "ms": "^2.1.1" } }, + "node_modules/postcss": { + "version": "8.4.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", + "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/postcss-selector-parser": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", @@ -9282,6 +9514,13 @@ "node": ">= 0.10" } }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -9403,6 +9642,14 @@ "react": "17.0.2" } }, + "node_modules/react-highlight": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/react-highlight/-/react-highlight-0.15.0.tgz", + "integrity": "sha512-5uV/b/N4Z421GSVVe05fz+OfTsJtFzx/fJBdafZyw4LS70XjIZwgEx3Lrkfc01W/RzZ2Dtfb0DApoaJFAIKBtA==", + "dependencies": { + "highlight.js": "^10.5.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -9679,6 +9926,12 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "node_modules/reserved-words": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz", + "integrity": "sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==", + "dev": true + }, "node_modules/resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -9797,9 +10050,9 @@ "dev": true }, "node_modules/sass": { - "version": "1.49.7", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.7.tgz", - "integrity": "sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz", + "integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -9810,7 +10063,7 @@ "sass": "sass.js" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/sass-loader": { @@ -9851,6 +10104,12 @@ } } }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "node_modules/scheduler": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", @@ -10140,9 +10399,9 @@ } }, "node_modules/source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true, "engines": { "node": ">=0.10.0" @@ -10319,6 +10578,15 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -10372,6 +10640,37 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz", "integrity": "sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg==" }, + "node_modules/stylus": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + }, + "bin": { + "stylus": "bin/stylus" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://opencollective.com/stylus" + } + }, + "node_modules/stylus/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -10592,15 +10891,6 @@ "json5": "lib/cli.js" } }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -10678,6 +10968,47 @@ "node": ">=4.2.0" } }, + "node_modules/typescript-plugin-css-modules": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/typescript-plugin-css-modules/-/typescript-plugin-css-modules-5.0.1.tgz", + "integrity": "sha512-hKXObfwfjx2/myRq4JeQ8D3xIWYTFqusi0hS/Aka7RFX1xQEoEkdOGDWyXNb8LmObawsUzbI30gQnZvqYXCrkA==", + "dev": true, + "dependencies": { + "@types/postcss-modules-local-by-default": "^4.0.0", + "@types/postcss-modules-scope": "^3.0.1", + "dotenv": "^16.0.3", + "icss-utils": "^5.1.0", + "less": "^4.1.3", + "lodash.camelcase": "^4.3.0", + "postcss": "^8.4.21", + "postcss-load-config": "^3.1.4", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "reserved-words": "^0.1.2", + "sass": "^1.58.3", + "source-map-js": "^1.0.2", + "stylus": "^0.59.0", + "tsconfig-paths": "^4.1.2" + }, + "peerDependencies": { + "typescript": ">=4.0.0" + } + }, + "node_modules/typescript-plugin-css-modules/node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -11458,6 +11789,12 @@ } }, "dependencies": { + "@adobe/css-tools": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", + "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==", + "dev": true + }, "@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", @@ -13718,6 +14055,24 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "@types/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-0VLab/pcLTLcfbxi6THSIMVYcw9hEUBGvjwwaGpW77mMgRXfGF+a76t7BxTGyLh1y68tBvrffp8UWnqvm76+yg==", + "dev": true, + "requires": { + "postcss": "^8.0.0" + } + }, + "@types/postcss-modules-scope": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/postcss-modules-scope/-/postcss-modules-scope-3.0.1.tgz", + "integrity": "sha512-LNkp3c4ML9EQj2dgslp4i80Jxj72YK3HjYzrTn6ftUVylW1zaKFGqrMlNIyqBmPWmIhZ/Y5r0Y4T49Hk1IuDUg==", + "dev": true, + "requires": { + "postcss": "^8.0.0" + } + }, "@types/prismjs": { "version": "1.16.6", "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.16.6.tgz", @@ -13769,6 +14124,15 @@ "@types/react": "*" } }, + "@types/react-highlight": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/react-highlight/-/react-highlight-0.12.5.tgz", + "integrity": "sha512-P8+mTxltxDdQ+99l+pjn40clziSbNrZy5d5zmvG+j3jKzokAhCoCZlIRmmnFgETTYubuqwKjvXSlvesBZcTfvQ==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-is": { "version": "17.0.3", "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", @@ -14981,6 +15345,15 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "requires": { + "is-what": "^3.14.1" + } + }, "copy-to-clipboard": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", @@ -15179,51 +15552,6 @@ "semver": "^7.3.5" }, "dependencies": { - "icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "requires": {} - }, - "postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", - "dev": true, - "requires": { - "nanoid": "^3.2.0", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "requires": {} - }, - "postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "dev": true, - "requires": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.4" - } - }, "postcss-modules-values": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", @@ -15241,12 +15569,6 @@ "requires": { "lru-cache": "^6.0.0" } - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true } } }, @@ -15540,6 +15862,12 @@ "tslib": "^2.0.3" } }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "dev": true + }, "dotenv-defaults": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", @@ -15615,6 +15943,16 @@ "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, + "errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "requires": { + "prr": "~1.0.1" + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -16680,6 +17018,11 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" + }, "history": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", @@ -16857,12 +17200,26 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "requires": {} + }, "ignore": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", "dev": true }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true + }, "immutable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", @@ -17137,6 +17494,12 @@ "call-bind": "^1.0.0" } }, + "is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, "is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -17282,12 +17645,9 @@ "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "jss": { "version": "10.8.2", @@ -17404,6 +17764,33 @@ "language-subtag-registry": "~0.3.2" } }, + "less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "requires": { + "copy-anything": "^2.0.1", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "parse-node-version": "^1.0.1", + "source-map": "~0.6.0", + "tslib": "^2.3.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, + "optional": true + } + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -17414,6 +17801,12 @@ "type-check": "~0.4.0" } }, + "lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -17448,6 +17841,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -17605,7 +18004,8 @@ "minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "mkdirp": { "version": "0.5.5", @@ -17651,9 +18051,9 @@ "dev": true }, "nanoid": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.0.tgz", - "integrity": "sha512-JzxqqT5u/x+/KOFSd7JP15DOo9nOoHpx6DYatqIHUW2+flybkm+mdcraotSQR5WcnZr+qhGVh8Ted0KdfSMxlg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "dev": true }, "natural-compare": { @@ -17662,6 +18062,40 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -17954,6 +18388,12 @@ "lines-and-columns": "^1.1.6" } }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true + }, "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", @@ -18104,6 +18544,54 @@ } } }, + "postcss": { + "version": "8.4.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", + "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "dev": true, + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, "postcss-selector-parser": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", @@ -18208,6 +18696,13 @@ "ipaddr.js": "1.9.1" } }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -18290,6 +18785,14 @@ "scheduler": "^0.20.2" } }, + "react-highlight": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/react-highlight/-/react-highlight-0.15.0.tgz", + "integrity": "sha512-5uV/b/N4Z421GSVVe05fz+OfTsJtFzx/fJBdafZyw4LS70XjIZwgEx3Lrkfc01W/RzZ2Dtfb0DApoaJFAIKBtA==", + "requires": { + "highlight.js": "^10.5.0" + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -18513,6 +19016,12 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "reserved-words": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz", + "integrity": "sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==", + "dev": true + }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -18592,9 +19101,9 @@ "dev": true }, "sass": { - "version": "1.49.7", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.7.tgz", - "integrity": "sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz", + "integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", @@ -18612,6 +19121,12 @@ "neo-async": "^2.6.2" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "scheduler": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", @@ -18856,9 +19371,9 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true }, "source-map-resolve": { @@ -19000,6 +19515,12 @@ "ansi-regex": "^5.0.1" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -19032,6 +19553,27 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz", "integrity": "sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg==" }, + "stylus": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", + "dev": true, + "requires": { + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -19197,12 +19739,6 @@ "requires": { "minimist": "^1.2.0" } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true } } }, @@ -19260,6 +19796,43 @@ "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", "dev": true }, + "typescript-plugin-css-modules": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/typescript-plugin-css-modules/-/typescript-plugin-css-modules-5.0.1.tgz", + "integrity": "sha512-hKXObfwfjx2/myRq4JeQ8D3xIWYTFqusi0hS/Aka7RFX1xQEoEkdOGDWyXNb8LmObawsUzbI30gQnZvqYXCrkA==", + "dev": true, + "requires": { + "@types/postcss-modules-local-by-default": "^4.0.0", + "@types/postcss-modules-scope": "^3.0.1", + "dotenv": "^16.0.3", + "icss-utils": "^5.1.0", + "less": "^4.1.3", + "lodash.camelcase": "^4.3.0", + "postcss": "^8.4.21", + "postcss-load-config": "^3.1.4", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "reserved-words": "^0.1.2", + "sass": "^1.58.3", + "source-map-js": "^1.0.2", + "stylus": "^0.59.0", + "tsconfig-paths": "^4.1.2" + }, + "dependencies": { + "tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "requires": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + } + } + }, "unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", diff --git a/web/package.json b/web/package.json index 95b1874..03b6f7f 100644 --- a/web/package.json +++ b/web/package.json @@ -25,6 +25,7 @@ "react": "^17.0.2", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^17.0.2", + "react-highlight": "^0.15.0", "react-monaco-editor": "^0.48.0", "react-router-dom": "^6.3.0", "react-toastify": "^9.0.1" @@ -41,6 +42,7 @@ "@types/react": "^17.0.37", "@types/react-copy-to-clipboard": "^5.0.2", "@types/react-dom": "^17.0.11", + "@types/react-highlight": "^0.12.5", "@types/react-router-dom": "^5.3.1", "babel-loader": "^8.2.3", "babel-plugin-prismjs": "^2.1.0", @@ -59,6 +61,7 @@ "style-loader": "^3.3.1", "ts-loader": "^9.2.6", "typescript": "^4.5.2", + "typescript-plugin-css-modules": "^5.0.1", "webpack": "5.64.3", "webpack-cli": "^4.9.2", "webpack-dev-server": "4.7.4" diff --git a/web/src/containers/Settings/internal/hooks/useAddPermission.tsx b/web/src/containers/Settings/internal/hooks/useAddPermission.tsx index f2d1761..c692bad 100644 --- a/web/src/containers/Settings/internal/hooks/useAddPermission.tsx +++ b/web/src/containers/Settings/internal/hooks/useAddPermission.tsx @@ -9,7 +9,7 @@ import { PermissionsContext } from '../../../../context/permissionsContext' import { findExistingPermission, findUpdatingPermission -} from '../../../../utils/helper' +} from '../../../../utils' const useAddPermission = () => { const { diff --git a/web/src/containers/Studio/editor.tsx b/web/src/containers/Studio/editor.tsx index 6a1b042..02964c1 100644 --- a/web/src/containers/Studio/editor.tsx +++ b/web/src/containers/Studio/editor.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, SetStateAction } from 'react' +import { Dispatch, SetStateAction } from 'react' import { Backdrop, @@ -17,10 +17,14 @@ import { TabContext, TabList, TabPanel } from '@mui/lab' import FilePathInputModal from '../../components/filePathInputModal' import FileMenu from './internal/components/fileMenu' import RunMenu from './internal/components/runMenu' +import LogComponent from './internal/components/log/logComponent' +import LogTabWithIcons from './internal/components/log/logTabWithIcons' import { usePrompt } from '../../utils/hooks' import { getLanguageFromExtension } from './internal/helper' import useEditor from './internal/hooks/useEditor' +import { RunTimeType } from '../../context/appContext' +import { LogObject } from '../../utils' const StyledTabPanel = styled(TabPanel)(() => ({ padding: '10px' @@ -108,6 +112,10 @@ const SASjsEditor = ({ /> ) + // INFO: variable indicating if selected run type is SAS if there are any errors or warnings in the log + const logWithErrorsOrWarnings = + selectedRunTime === RunTimeType.SAS && log && typeof log === 'object' + return ( - + + ) : ( + '' + ) + } + onClick={() => { + const logWrapper = document.querySelector(`#logWrapper`) + + if (logWrapper) logWrapper.scrollTop = 0 + }} + /> @@ -195,15 +218,9 @@ const SASjsEditor = ({ -
-

Log

-
-                {log}
-              
-
+ {log && ( + + )}
diff --git a/web/src/containers/Studio/internal/components/log/log.module.css b/web/src/containers/Studio/internal/components/log/log.module.css new file mode 100644 index 0000000..0070ee0 --- /dev/null +++ b/web/src/containers/Studio/internal/components/log/log.module.css @@ -0,0 +1,86 @@ +.ChunkHeader { + color: #444; + cursor: pointer; + padding: 18px; + width: 100%; + text-align: left; + border: none; + outline: none; + transition: 0.4s; + box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 1px -1px, + rgba(0, 0, 0, 0.14) 0px 1px 1px 0px, rgba(0, 0, 0, 0.12) 0px 1px 3px 0px; +} + +.ChunkDetails { + display: flex; + flex-direction: row; + gap: 6px; + align-items: center; +} + +.ChunkExpandIcon { + margin-left: auto; +} + +.ChunkBody { + background-color: white; + overflow: hidden; +} + +.ChunksContainer { + display: flex; + flex-direction: column; + gap: 10px; +} + +.LogContainer { + background-color: #fbfbfb; + border: 1px solid #e2e2e2; + border-radius: 3px; + min-height: 50px; + padding: 10px; + box-sizing: border-box; + white-space: pre-wrap; + font-family: Monaco, Courier, monospace; + position: relative; + width: 100%; +} + +.LogWrapper { + overflow-y: auto; + max-height: calc(100vh - 130px); +} + +.LogBody { + overflow: auto; + height: calc(100vh - 220px); +} + +.TreeContainer { + background-color: white; + padding-top: 10px; + padding-bottom: 10px; +} + +.TabContainer { + display: flex; + flex-direction: row; + gap: 6px; + align-items: center; +} + +.TabDownloadIcon { + margin-left: 20px; +} + +.HighlightedLine { + background-color: #f6e30599; +} + +.Icon { + font-size: 20px !important; +} + +.GreenIcon { + color: green; +} diff --git a/web/src/containers/Studio/internal/components/log/logChunk.tsx b/web/src/containers/Studio/internal/components/log/logChunk.tsx new file mode 100644 index 0000000..3f6b936 --- /dev/null +++ b/web/src/containers/Studio/internal/components/log/logChunk.tsx @@ -0,0 +1,168 @@ +import { useState, useEffect, SyntheticEvent } from 'react' +import { Typography } from '@mui/material' +import Highlight from 'react-highlight' +import { ErrorOutline, Warning } from '@mui/icons-material' +import ContentCopyIcon from '@mui/icons-material/ContentCopy' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' +import CheckIcon from '@mui/icons-material/Check' +import FileDownloadIcon from '@mui/icons-material/FileDownload' +import { + defaultChunkSize, + parseErrorsAndWarnings, + LogInstance, + clearErrorsAndWarningsHtmlWrapping, + download +} from '../../../../../utils' +import { logStyles } from './logComponent' +import classes from './log.module.css' + +interface LogChunkProps { + id: number + text: string + expanded: boolean + logLineCount: number + onClick: (evt: any, id: number) => void + scrollToLogInstance?: LogInstance + updated: number +} + +const LogChunk = (props: LogChunkProps) => { + const { id, text, logLineCount } = props + const [scrollToLogInstance, setScrollToLogInstance] = useState( + props.scrollToLogInstance + ) + const rowText = clearErrorsAndWarningsHtmlWrapping(text) + const styles = logStyles() + const [expanded, setExpanded] = useState(props.expanded) + const [copied, setCopied] = useState(false) + + useEffect(() => { + setExpanded(props.expanded) + }, [props.expanded]) + + useEffect(() => { + if (props.expanded !== expanded) { + setExpanded(props.expanded) + } + + if ( + props.scrollToLogInstance && + props.scrollToLogInstance !== scrollToLogInstance + ) { + setScrollToLogInstance(props.scrollToLogInstance) + } + }, [props]) + + useEffect(() => { + if (expanded && scrollToLogInstance) { + const { type, id } = scrollToLogInstance + const line = document.getElementById(`${type}_${id}`) + const logWrapper: HTMLDivElement | null = + document.querySelector(`#logWrapper`) + const logContainer: HTMLHeadElement | null = + document.querySelector(`#log_container`) + + if (line && logWrapper && logContainer) { + line.className = classes.HighlightedLine + + line.scrollIntoView({ behavior: 'smooth', block: 'start' }) + + setTimeout(() => { + line.classList.remove(classes.HighlightedLine) + + setScrollToLogInstance(undefined) + }, 3000) + } + } + }, [expanded, scrollToLogInstance, props]) + + const { errors, warnings } = parseErrorsAndWarnings(text) + + const getLineRange = (separator = ' ... ') => + `${id * defaultChunkSize}${separator}${ + (id + 1) * defaultChunkSize < logLineCount + ? (id + 1) * defaultChunkSize + : logLineCount + }` + + return ( +
props.onClick(evt, id)}> + +
+
+ + {expanded ? text : ''} + +
+
+
+ ) +} + +export default LogChunk diff --git a/web/src/containers/Studio/internal/components/log/logComponent.tsx b/web/src/containers/Studio/internal/components/log/logComponent.tsx new file mode 100644 index 0000000..ab38642 --- /dev/null +++ b/web/src/containers/Studio/internal/components/log/logComponent.tsx @@ -0,0 +1,243 @@ +import { useEffect, useState } from 'react' +import TreeView from '@mui/lab/TreeView' +import TreeItem from '@mui/lab/TreeItem' +import { ChevronRight, ExpandMore } from '@mui/icons-material' +import { Typography } from '@mui/material' +import { ListItemText } from '@mui/material' +import { makeStyles } from '@mui/styles' +import Highlight from 'react-highlight' +import { LogObject, defaultChunkSize } from '../../../../../utils' +import { RunTimeType } from '../../../../../context/appContext' +import { splitIntoChunks, LogInstance } from '../../../../../utils' +import LogChunk from './logChunk' +import classes from './log.module.css' + +export const logStyles: any = makeStyles((theme: any) => ({ + expansionDescription: { + [theme.breakpoints.down('sm')]: { + fontSize: theme.typography.pxToRem(12) + }, + [theme.breakpoints.up('md')]: { + fontSize: theme.typography.pxToRem(16) + } + } +})) + +interface LogComponentProps { + log: LogObject | string + selectedRunTime: RunTimeType | string +} + +const LogComponent = (props: LogComponentProps) => { + const { log, selectedRunTime } = props + const logObject = log as LogObject + const logChunks = splitIntoChunks(logObject?.body || '') + const [logChunksState, setLogChunksState] = useState( + new Array(logChunks.length).fill(false) + ) + const [scrollToLogInstance, setScrollToLogInstance] = useState() + const [oldestExpandedChunk, setOldestExpandedChunk] = useState( + logChunksState.length - 1 + ) + const maxOpenedChunks = 2 + + const styles = logStyles() + + const goToLogLine = (logInstance: LogInstance, ind: number) => { + let chunkNumber = 0 + + for ( + let i = 0; + i <= Math.ceil(logObject.linesCount / defaultChunkSize); + i++ + ) { + if (logInstance.line < (i + 1) * defaultChunkSize) { + chunkNumber = i + + break + } + } + + setLogChunksState((prevState) => { + const newState = [...prevState] + newState[chunkNumber] = true + + const chunkToCollapse = getChunkToAutoCollapse() + + if (chunkToCollapse !== undefined) { + newState[chunkToCollapse] = false + } + + return newState + }) + + setScrollToLogInstance(logInstance) + } + + useEffect(() => { + // INFO: expand the last chunk by default + setLogChunksState((prevState) => { + const lastChunk = prevState.length - 1 + + const newState = [...prevState] + newState[lastChunk] = true + + return newState + }) + + setTimeout(() => { + scrollToTheBottom() + }, 100) + }, []) + + // INFO: scroll to the bottom of the log + const scrollToTheBottom = () => { + const logWrapper: HTMLDivElement | null = + document.querySelector(`#logWrapper`) + + if (logWrapper) { + logWrapper.scrollTop = logWrapper.scrollHeight + } + } + + const getChunkToAutoCollapse = () => { + const openedChunks = logChunksState + .map((chunkState: boolean, id: number) => (chunkState ? id : undefined)) + .filter((chunk) => chunk !== undefined) + + if (openedChunks.length < maxOpenedChunks) return undefined + else { + const chunkToCollapse = oldestExpandedChunk + const newOldestChunk = openedChunks.filter( + (chunk) => chunk !== chunkToCollapse + )[0] + + if (newOldestChunk !== undefined) { + setOldestExpandedChunk(newOldestChunk) + + return chunkToCollapse + } + + return undefined + } + } + + const hasErrorsOrWarnings = + logObject.errors?.length !== 0 || logObject.warnings?.length !== 0 + const logBody = typeof log === 'string' ? log : log.body + + return ( + <> + {selectedRunTime === RunTimeType.SAS && logObject.body ? ( +
+
+ {hasErrorsOrWarnings && ( +
+ } + defaultExpandIcon={} + > + {logObject.errors && logObject.errors.length !== 0 && ( + + {`Errors (${logObject.errors.length})`} + + } + > + {logObject.errors && + logObject.errors.map((error, ind) => ( + } + key={`error_${ind}`} + onClick={() => goToLogLine(error, ind)} + /> + ))} + + )} + {logObject.warnings && logObject.warnings.length !== 0 && ( + {`Warnings (${logObject.warnings.length})`} + } + > + {logObject.warnings && + logObject.warnings.map((warning, ind) => ( + } + key={`warning_${ind}`} + onClick={() => goToLogLine(warning, ind)} + /> + ))} + + )} + +
+ )} +
+
+ {Array.isArray(logChunks) ? ( + logChunks.map((chunk: string, id: number) => ( + { + setLogChunksState((prevState) => { + const newState = [...prevState] + const expand = !newState[chunkNumber] + + newState[chunkNumber] = expand + + if (expand) { + const chunkToCollapse = getChunkToAutoCollapse() + + if (chunkToCollapse !== undefined) { + newState[chunkToCollapse] = false + } + } + + return newState + }) + + setScrollToLogInstance(undefined) + }} + /> + )) + ) : ( + + + {logChunks} + + + )} +
+
+ ) : ( +
+

Log

+
+            {logBody}
+          
+
+ )} + + ) +} + +export default LogComponent diff --git a/web/src/containers/Studio/internal/components/log/logTabWithIcons.tsx b/web/src/containers/Studio/internal/components/log/logTabWithIcons.tsx new file mode 100644 index 0000000..761ee18 --- /dev/null +++ b/web/src/containers/Studio/internal/components/log/logTabWithIcons.tsx @@ -0,0 +1,41 @@ +import { ErrorOutline, Warning } from '@mui/icons-material' +import FileDownloadIcon from '@mui/icons-material/FileDownload' +import { + LogObject, + download, + clearErrorsAndWarningsHtmlWrapping +} from '../../../../../utils' +import Tooltip from '@mui/material/Tooltip' +import classes from './log.module.css' + +interface LogTabProps { + log: LogObject +} + +const LogTabWithIcons = (props: LogTabProps) => { + const { errors, warnings, body } = props.log + + return ( +
+ log + {errors && errors.length !== 0 && ( + + )} + {warnings && warnings.length !== 0 && ( + + )} + { + download(evt, clearErrorsAndWarningsHtmlWrapping(body)) + }} + > + + +
+ ) +} + +export default LogTabWithIcons diff --git a/web/src/containers/Studio/internal/hooks/useEditor.ts b/web/src/containers/Studio/internal/hooks/useEditor.ts index 43bcba2..5baba2d 100644 --- a/web/src/containers/Studio/internal/hooks/useEditor.ts +++ b/web/src/containers/Studio/internal/hooks/useEditor.ts @@ -18,6 +18,7 @@ import { useSnackbar, useStateWithCallback } from '../../../../utils/hooks' +import { parseErrorsAndWarnings, LogObject } from '../../../../utils' const SASJS_LOGS_SEPARATOR = 'SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784' @@ -41,10 +42,12 @@ const useEditor = ({ const [prevFileContent, setPrevFileContent] = useStateWithCallback('') const [fileContent, setFileContent] = useState('') - const [log, setLog] = useState('') + const [log, setLog] = useState() const [webout, setWebout] = useState('') const [runTimes, setRunTimes] = useState([]) - const [selectedRunTime, setSelectedRunTime] = useState('') + const [selectedRunTime, setSelectedRunTime] = useState( + '' + ) const [selectedFileExtension, setSelectedFileExtension] = useState('') const [openFilePathInputModal, setOpenFilePathInputModal] = useState(false) const [showDiff, setShowDiff] = useState(false) @@ -150,6 +153,13 @@ const useEditor = ({ const runCode = useCallback( (code: string) => { setIsLoading(true) + + // Scroll to bottom of log + const logElement = document.getElementById('log') + if (logElement) logElement.scrollTop = logElement.scrollHeight + + setIsLoading(false) + axios .post(`/SASjsApi/code/execute`, { code: programPathInjection( @@ -160,8 +170,24 @@ const useEditor = ({ runTime: selectedRunTime }) .then((res: any) => { + if (selectedRunTime === RunTimeType.SAS) { + const { errors, warnings, logLines } = parseErrorsAndWarnings( + res.data.split(SASJS_LOGS_SEPARATOR)[1] + ) + + const log: LogObject = { + body: logLines.join(`\n`), + errors, + warnings, + linesCount: logLines.length + } + + setLog(log) + } else { + setLog(res.data.split(SASJS_LOGS_SEPARATOR)[1] ?? '') + } + setWebout(res.data.split(SASJS_LOGS_SEPARATOR)[0] ?? '') - setLog(res.data.split(SASJS_LOGS_SEPARATOR)[1] ?? '') setTab('log') // Scroll to bottom of log @@ -249,7 +275,7 @@ const useEditor = ({ }, [appContext.runTimes]) useEffect(() => { - if (runTimes.length) setSelectedRunTime(runTimes[0]) + if (runTimes.length) setSelectedRunTime(runTimes[0] as RunTimeType) }, [runTimes]) useEffect(() => { @@ -280,7 +306,6 @@ const useEditor = ({ const content = localStorage.getItem('fileContent') ?? '' setFileContent(content) } - setLog('') setWebout('') setTab('code') // eslint-disable-next-line react-hooks/exhaustive-deps @@ -294,7 +319,9 @@ const useEditor = ({ useEffect(() => { const fileExtension = selectedFileExtension.toLowerCase() - if (runTimes.includes(fileExtension)) setSelectedRunTime(fileExtension) + + if (runTimes.includes(fileExtension)) + setSelectedRunTime(fileExtension as RunTimeType) }, [selectedFileExtension, runTimes]) return { diff --git a/web/src/types/declaration.d.ts b/web/src/types/declaration.d.ts new file mode 100644 index 0000000..d1c3d9e --- /dev/null +++ b/web/src/types/declaration.d.ts @@ -0,0 +1,4 @@ +declare module '*.module.css' { + const classes: { [key: string]: string } + export default classes +} diff --git a/web/src/utils/index.ts b/web/src/utils/index.ts new file mode 100644 index 0000000..f5f22bb --- /dev/null +++ b/web/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './log' +export * from './types' +export * from './helper' diff --git a/web/src/utils/log.ts b/web/src/utils/log.ts new file mode 100644 index 0000000..466787b --- /dev/null +++ b/web/src/utils/log.ts @@ -0,0 +1,133 @@ +import { SyntheticEvent } from 'react' +import { LogInstance } from './' + +export const parseErrorsAndWarnings = (log: string) => { + const logLines = log.split('\n') + const errorLines: LogInstance[] = [] + const warningLines: LogInstance[] = [] + + logLines.forEach((line: string, index: number) => { + // INFO: check if content in element starts with ERROR + if (/<.*>ERROR/gm.test(line)) { + const errorLine = line.substring(line.indexOf('E'), line.length - 1) + + errorLines.push({ + body: errorLine, + line: index, + type: 'error', + id: errorLines.length + }) + } + + // INFO: check if line starts with ERROR + else if (/^ERROR/gm.test(line)) { + errorLines.push({ + body: line, + line: index, + type: 'error', + id: errorLines.length + }) + + logLines[index] = + `` + + logLines[index] + + '' + } + + // INFO: check if content in element starts with WARNING + else if (/<.*>WARNING/gm.test(line)) { + const warningLine = line.substring(line.indexOf('W'), line.length - 1) + + warningLines.push({ + body: warningLine, + line: index, + type: 'warning', + id: warningLines.length + }) + } + + // INFO: check if line starts with WARNING + else if (/^WARNING/gm.test(line)) { + warningLines.push({ + body: line, + line: index, + type: 'warning', + id: warningLines.length + }) + + logLines[index] = + `` + + logLines[index] + + '' + } + }) + + return { errors: errorLines, warnings: warningLines, logLines } +} + +export const defaultChunkSize = 20000 + +export const isTheLastChunk = ( + lineCount: number, + chunkNumber: number, + chunkSize = defaultChunkSize +) => { + if (lineCount <= chunkSize) return true + + const chunksNumber = Math.ceil(lineCount / chunkSize) + + return chunkNumber === chunksNumber +} + +export const splitIntoChunks = (log: string, chunkSize = defaultChunkSize) => { + if (!log) return [] + + const logLines: string[] = log.split(`\n`) + + if (logLines.length <= chunkSize) return [log] + + const chunks: string[] = [] + + while (logLines.length) { + const chunk = logLines.splice(0, chunkSize) + + chunks.push(chunk.join(`\n`)) + } + + return chunks +} + +export const clearErrorsAndWarningsHtmlWrapping = (log: string) => + log.replace(/^]*>/gm, '').replace(/<\/font>/gm, '') + +export const download = (evt: SyntheticEvent, log: string, fileName = '') => { + evt.stopPropagation() + + const padWithZero = (num: number) => (num < 9 ? `0${num}` : `${num}`) + + const date = new Date() + const datePrefix = [ + date.getFullYear(), + padWithZero(date.getMonth() + 1), + padWithZero(date.getDate()), + padWithZero(date.getHours()), + padWithZero(date.getMinutes()), + padWithZero(date.getSeconds()) + ].join('') + + const file = new Blob([log]) + const url = URL.createObjectURL(file) + + const a = document.createElement('a') + a.href = url + a.download = `${datePrefix}${fileName}.log` + document.body.appendChild(a) + a.click() + + setTimeout(() => { + document.body.removeChild(a) + window.URL.revokeObjectURL(url) + }, 0) +} diff --git a/web/src/utils/types.ts b/web/src/utils/types.ts index c733e23..8a10b5a 100644 --- a/web/src/utils/types.ts +++ b/web/src/utils/types.ts @@ -39,3 +39,18 @@ export interface TreeNode { isFolder: boolean children: Array } + +export interface LogInstance { + body: string + line: number + type: 'error' | 'warning' + id: number + ref?: any +} + +export interface LogObject { + body: string + errors?: LogInstance[] + warnings?: LogInstance[] + linesCount: number +} diff --git a/web/tsconfig.json b/web/tsconfig.json index 9d379a3..6ebad81 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -14,7 +14,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "plugins": [{ "name": "typescript-plugin-css-modules" }] }, "include": ["src"] } diff --git a/web/webpack.common.ts b/web/webpack.common.ts index 7375ed6..dc493d0 100644 --- a/web/webpack.common.ts +++ b/web/webpack.common.ts @@ -32,9 +32,18 @@ const config: Configuration = { ] }, { - test: /\.css$/, - exclude: ['/node_modules/'], - use: ['style-loader', 'css-loader'] + test: /\.css$/i, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + modules: { + localIdentName: '[local]--[hash:base64:5]' + } + } + } + ] }, { test: /\.scss$/,