diff --git a/ui/.prettierrc b/ui/.prettierrc new file mode 100644 index 000000000..f40419ad7 --- /dev/null +++ b/ui/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "semi": false, + "arrowParens": "avoid", + "trailingComma": "all" +} diff --git a/ui/angular.json b/ui/angular.json index c5bf16681..a9a178e5c 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -94,10 +94,7 @@ "lint": { "builder": "@angular-eslint/builder:lint", "options": { - "lintFilePatterns": [ - "src/**/*.ts", - "src/**/*.html" - ] + "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] } }, "ionic-cordova-build": { diff --git a/ui/package-lock.json b/ui/package-lock.json index 2b55230a5..1334e1be0 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -24,6 +24,7 @@ "dompurify": "^2.3.3", "fast-json-patch": "^3.1.0", "fuse.js": "^6.4.6", + "lint-staged": "^12.1.2", "marked": "3.0.2", "mustache": "^4.2.0", "ng-qrcode": "^5.0.0", @@ -44,7 +45,9 @@ "@types/mustache": "^4.1.2", "@types/node": "^16.7.13", "@types/uuid": "^8.3.1", + "husky": "^4.3.8", "node-html-parser": "^4.1.4", + "prettier": "^2.5.1", "raw-loader": "^4.0.2", "ts-node": "^10.2.0", "tslint": "^6.1.3", @@ -3500,7 +3503,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -3594,7 +3596,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, "engines": { "node": ">=6" } @@ -3603,7 +3604,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -3630,7 +3630,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -3821,6 +3820,14 @@ "node": ">=0.10.0" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -4228,7 +4235,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -4530,6 +4536,12 @@ "node": ">=6.0" } }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, "node_modules/circular-dependency-plugin": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz", @@ -4644,7 +4656,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, "engines": { "node": ">=6" } @@ -4653,7 +4664,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, "dependencies": { "restore-cursor": "^3.1.0" }, @@ -4673,6 +4683,78 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.0.1.tgz", + "integrity": "sha512-5ohWO/M4//8lErlUUtrFy3b11GtNOuMOU0ysKCDXFcfXuuvUXu95akgj/i8ofmaGdN0hCqyl6uu9i8dS/mQp5g==", + "dependencies": { + "emoji-regex": "^9.2.2", + "is-fullwidth-code-point": "^4.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -4790,6 +4872,12 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "node_modules/compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "dev": true + }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -5755,7 +5843,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -6110,8 +6197,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/emojis-list": { "version": "3.0.0", @@ -6176,6 +6262,17 @@ "node": ">=10.13.0" } }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -7019,7 +7116,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7090,6 +7186,21 @@ "node": ">=8" } }, + "node_modules/find-versions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", + "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", + "dev": true, + "dependencies": { + "semver-regex": "^3.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/flatten": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", @@ -7921,6 +8032,14 @@ "node": ">= 6" } }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -7930,6 +8049,164 @@ "ms": "^2.0.0" } }, + "node_modules/husky": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz", + "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "compare-versions": "^3.6.0", + "cosmiconfig": "^7.0.0", + "find-versions": "^4.0.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^5.0.0", + "please-upgrade-node": "^3.2.0", + "slash": "^3.0.0", + "which-pm-runs": "^1.0.0" + }, + "bin": { + "husky-run": "bin/run.js", + "husky-upgrade": "lib/upgrader/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/husky" + } + }, + "node_modules/husky/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/husky/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/husky/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/husky/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/husky/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/husky/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/husky/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/husky/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/husky/node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/husky/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -8127,7 +8404,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, "engines": { "node": ">=8" } @@ -8478,7 +8754,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -8514,7 +8789,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -8648,8 +8922,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "node_modules/isobject": { "version": "3.0.1", @@ -9010,7 +9283,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", - "dev": true, "engines": { "node": ">=10" } @@ -9021,6 +9293,257 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, + "node_modules/lint-staged": { + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.2.tgz", + "integrity": "sha512-bSMcQVqMW98HLLLR2c2tZ+vnDCnx4fd+0QJBQgN/4XkdspGRPc8DGp7UuOEBe1ApCfJ+wXXumYnJmU+wDo7j9A==", + "dependencies": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.16", + "commander": "^8.3.0", + "debug": "^4.3.2", + "enquirer": "^2.3.6", + "execa": "^5.1.1", + "lilconfig": "2.0.4", + "listr2": "^3.13.3", + "micromatch": "^4.0.4", + "normalize-path": "^3.0.0", + "object-inspect": "^1.11.0", + "string-argv": "^0.3.1", + "supports-color": "^9.0.2", + "yaml": "^1.10.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==" + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/lint-staged/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/supports-color": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.1.tgz", + "integrity": "sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/listr2": { + "version": "3.13.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.13.5.tgz", + "integrity": "sha512-3n8heFQDSk+NcwBn3CgxEibZGaRzx+pC64n3YjpMD1qguV4nWus3Al+Oo3KooqFKTQEJ1v7MmnbnyyNspgx3NA==", + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.4.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/listr2/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/listr2/node_modules/colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==" + }, + "node_modules/listr2/node_modules/rxjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", + "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", + "dependencies": { + "tslib": "~2.1.0" + } + }, + "node_modules/listr2/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + }, "node_modules/loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -9174,6 +9697,82 @@ "node": ">=8" } }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-update/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/loglevel": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", @@ -9428,8 +10027,7 @@ "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "node_modules/merge2": { "version": "1.4.1", @@ -9453,7 +10051,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, "dependencies": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -9499,7 +10096,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "engines": { "node": ">=6" } @@ -9693,8 +10289,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multicast-dns": { "version": "6.2.3", @@ -10029,7 +10624,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10294,6 +10888,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.1.tgz", + "integrity": "sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", @@ -10401,7 +11003,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -10429,6 +11030,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "dev": true, + "bin": { + "opencollective-postinstall": "index.js" + } + }, "node_modules/opn": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", @@ -10625,7 +11235,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -10887,7 +11496,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -10951,6 +11559,15 @@ "node": ">=8" } }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "dependencies": { + "semver-compare": "^1.0.0" + } + }, "node_modules/pngjs": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", @@ -13007,6 +13624,18 @@ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", "dev": true }, + "node_modules/prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -13761,7 +14390,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -13798,6 +14426,11 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -13985,6 +14618,24 @@ "node": ">=10" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, + "node_modules/semver-regex": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.3.tgz", + "integrity": "sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -14197,8 +14848,7 @@ "node_modules/signal-exit": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", - "dev": true + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" }, "node_modules/slash": { "version": "3.0.0", @@ -14209,6 +14859,43 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", + "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -14814,11 +15501,18 @@ } ] }, + "node_modules/string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14832,7 +15526,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -14849,6 +15542,14 @@ "node": ">=0.10.0" } }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, "node_modules/style-loader": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.2.1.tgz", @@ -15195,8 +15896,7 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "node_modules/thunky": { "version": "1.1.0", @@ -15274,7 +15974,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -15457,7 +16156,6 @@ "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, "engines": { "node": ">=10" }, @@ -16697,7 +17395,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -16713,6 +17410,12 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "node_modules/which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", + "dev": true + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -16732,7 +17435,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -16749,7 +17451,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -16764,7 +17465,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -16775,8 +17475,7 @@ "node_modules/wrap-ansi/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/wrappy": { "version": "1.0.2", @@ -16824,7 +17523,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, "engines": { "node": ">= 6" } @@ -19421,7 +20119,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -19493,14 +20190,12 @@ "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, "requires": { "type-fest": "^0.21.3" } @@ -19514,8 +20209,7 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "3.2.1", @@ -19669,6 +20363,11 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" + }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -20000,7 +20699,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -20235,6 +20933,12 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, "circular-dependency-plugin": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz", @@ -20325,14 +21029,12 @@ "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, "requires": { "restore-cursor": "^3.1.0" } @@ -20343,6 +21045,50 @@ "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", "dev": true }, + "cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==" + }, + "string-width": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.0.1.tgz", + "integrity": "sha512-5ohWO/M4//8lErlUUtrFy3b11GtNOuMOU0ysKCDXFcfXuuvUXu95akgj/i8ofmaGdN0hCqyl6uu9i8dS/mQp5g==", + "requires": { + "emoji-regex": "^9.2.2", + "is-fullwidth-code-point": "^4.0.0", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, "cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -20439,6 +21185,12 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -21154,7 +21906,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -21450,8 +22201,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "emojis-list": { "version": "3.0.0", @@ -21506,6 +22256,14 @@ "tapable": "^2.2.0" } }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "requires": { + "ansi-colors": "^4.1.1" + } + }, "entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -22131,7 +22889,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -22189,6 +22946,15 @@ "path-exists": "^4.0.0" } }, + "find-versions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", + "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", + "dev": true, + "requires": { + "semver-regex": "^3.1.2" + } + }, "flatten": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", @@ -22857,6 +23623,11 @@ "debug": "4" } }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" + }, "humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -22866,6 +23637,112 @@ "ms": "^2.0.0" } }, + "husky": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz", + "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "compare-versions": "^3.6.0", + "cosmiconfig": "^7.0.0", + "find-versions": "^4.0.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^5.0.0", + "please-upgrade-node": "^3.2.0", + "slash": "^3.0.0", + "which-pm-runs": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "requires": { + "find-up": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -22997,8 +23874,7 @@ "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" }, "indexes-of": { "version": "1.0.1", @@ -23266,8 +24142,7 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { "version": "4.0.3", @@ -23293,8 +24168,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-path-cwd": { "version": "2.2.0", @@ -23392,8 +24266,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -23672,8 +24545,7 @@ "lilconfig": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", - "dev": true + "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==" }, "lines-and-columns": { "version": "1.1.6", @@ -23681,6 +24553,181 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, + "lint-staged": { + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.2.tgz", + "integrity": "sha512-bSMcQVqMW98HLLLR2c2tZ+vnDCnx4fd+0QJBQgN/4XkdspGRPc8DGp7UuOEBe1ApCfJ+wXXumYnJmU+wDo7j9A==", + "requires": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.16", + "commander": "^8.3.0", + "debug": "^4.3.2", + "enquirer": "^2.3.6", + "execa": "^5.1.1", + "lilconfig": "2.0.4", + "listr2": "^3.13.3", + "micromatch": "^4.0.4", + "normalize-path": "^3.0.0", + "object-inspect": "^1.11.0", + "string-argv": "^0.3.1", + "supports-color": "^9.0.2", + "yaml": "^1.10.2" + }, + "dependencies": { + "colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==" + }, + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "requires": { + "path-key": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "supports-color": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.1.tgz", + "integrity": "sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==" + } + } + }, + "listr2": { + "version": "3.13.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.13.5.tgz", + "integrity": "sha512-3n8heFQDSk+NcwBn3CgxEibZGaRzx+pC64n3YjpMD1qguV4nWus3Al+Oo3KooqFKTQEJ1v7MmnbnyyNspgx3NA==", + "requires": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.4.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==" + }, + "rxjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", + "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", + "requires": { + "tslib": "~2.1.0" + } + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + } + } + }, "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -23800,6 +24847,60 @@ } } }, + "log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "requires": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "loglevel": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", @@ -24007,8 +25108,7 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "merge2": { "version": "1.4.1", @@ -24026,7 +25126,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -24056,8 +25155,7 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "mini-css-extract-plugin": { "version": "2.4.2", @@ -24198,8 +25296,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multicast-dns": { "version": "6.2.3", @@ -24455,8 +25552,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-range": { "version": "0.1.2", @@ -24665,6 +25761,11 @@ } } }, + "object-inspect": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.1.tgz", + "integrity": "sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA==" + }, "object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", @@ -24745,7 +25846,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -24761,6 +25861,12 @@ "is-wsl": "^2.2.0" } }, + "opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "dev": true + }, "opn": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", @@ -24906,7 +26012,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, "requires": { "aggregate-error": "^3.0.0" } @@ -25134,8 +26239,7 @@ "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" }, "pify": { "version": "2.3.0", @@ -25179,6 +26283,15 @@ "find-up": "^4.0.0" } }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, "pngjs": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", @@ -26684,6 +27797,12 @@ "uniq": "^1.0.1" } }, + "prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "dev": true + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -27263,7 +28382,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, "requires": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -27287,6 +28405,11 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -27407,6 +28530,18 @@ "lru-cache": "^6.0.0" } }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, + "semver-regex": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.3.tgz", + "integrity": "sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ==", + "dev": true + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -27597,8 +28732,7 @@ "signal-exit": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", - "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", - "dev": true + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" }, "slash": { "version": "3.0.0", @@ -27606,6 +28740,27 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", + "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==" + }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==" + } + } + }, "smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -28097,11 +29252,15 @@ } } }, + "string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==" + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -28112,7 +29271,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -28123,6 +29281,11 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" + }, "style-loader": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.2.1.tgz", @@ -28368,8 +29531,7 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "thunky": { "version": "1.1.0", @@ -28434,7 +29596,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -28563,8 +29724,7 @@ "type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" }, "type-is": { "version": "1.6.18", @@ -29544,7 +30704,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -29554,6 +30713,12 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", + "dev": true + }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -29573,7 +30738,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -29584,7 +30748,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -29593,7 +30756,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -29601,8 +30763,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" } } }, @@ -29634,8 +30795,7 @@ "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "yargs": { "version": "17.2.1", diff --git a/ui/package.json b/ui/package.json index 82ea45c63..2c6ba497a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -37,6 +37,13 @@ "uuid": "^8.3.2", "zone.js": "^0.11.4" }, + "lint-staged": { + "*.{js,ts,html,md,less,json}": [ + "prettier --write", + "git add" + ], + "*.ts": "tslint" + }, "devDependencies": { "@angular-devkit/build-angular": "^12.2.4", "@angular/cli": "^12.2.4", @@ -49,7 +56,10 @@ "@types/mustache": "^4.1.2", "@types/node": "^16.7.13", "@types/uuid": "^8.3.1", + "husky": "^4.3.8", + "lint-staged": "^12.1.2", "node-html-parser": "^4.1.4", + "prettier": "^2.5.1", "raw-loader": "^4.0.2", "ts-node": "^10.2.0", "tslint": "^6.1.3", diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index 6e10ef844..404672c77 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -8,9 +8,7 @@ import { HttpClientModule } from '@angular/common/http' import { AppComponent } from './app.component' import { AppRoutingModule } from './app-routing.module' import { ApiService } from './services/api/embassy-api.service' -import { ApiServiceFactory } from './services/api/api.service.factory' import { PatchDbServiceFactory } from './services/patch-db/patch-db.factory' -import { HttpService } from './services/http.service' import { ConfigService } from './services/config.service' import { QrCodeModule } from 'ng-qrcode' import { OSWelcomePageModule } from './modals/os-welcome/os-welcome.module' @@ -22,6 +20,10 @@ import { FormBuilder } from '@angular/forms' import { GenericInputComponentModule } from './modals/generic-input/generic-input.component.module' import { AuthService } from './services/auth.service' import { GlobalErrorHandler } from './services/global-error-handler.service' +import { MockApiService } from './services/api/embassy-mock-api.service' +import { LiveApiService } from './services/api/embassy-live-api.service' + +const { mocks } = require('../../config.json') @NgModule({ declarations: [AppComponent], @@ -54,8 +56,7 @@ import { GlobalErrorHandler } from './services/global-error-handler.service' }, { provide: ApiService, - useFactory: ApiServiceFactory, - deps: [ConfigService, HttpService, LocalStorageBootstrap], + useClass: mocks.enabled ? MockApiService : LiveApiService, }, { provide: PatchDbService, diff --git a/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html b/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html index a3c6a8c0a..d446c324a 100644 --- a/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html +++ b/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html @@ -24,7 +24,7 @@ slot="end" fill="clear" color="primary" - (click)="launchUi(pkg.entry)" + (click)="launchUi()" [disabled]="!(pkg.entry.state | isLaunchable: status:manifest.interfaces)" > diff --git a/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts b/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts index 4cc739713..572bcce36 100644 --- a/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts +++ b/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts @@ -1,47 +1,40 @@ -import { DOCUMENT } from "@angular/common"; import { ChangeDetectionStrategy, Component, Inject, Input, -} from "@angular/core"; +} from '@angular/core' import { PackageMainStatus, - PackageDataEntry, Manifest, -} from "src/app/services/patch-db/data-model"; -import { ConfigService } from "src/app/services/config.service"; -import { PkgInfo } from "src/app/util/get-package-info"; + PackageDataEntry, + Manifest, +} from 'src/app/services/patch-db/data-model' +import { PkgInfo } from 'src/app/util/get-package-info' +import { UiLauncherService } from 'src/app/services/ui-launcher.service' @Component({ - selector: "app-list-pkg", - templateUrl: "app-list-pkg.component.html", + selector: 'app-list-pkg', + templateUrl: 'app-list-pkg.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppListPkgComponent { @Input() - pkg: PkgInfo; + pkg: PkgInfo @Input() - connectionFailure = false; + connectionFailure = false - constructor( - @Inject(DOCUMENT) private readonly document: Document, - private readonly config: ConfigService - ) {} + constructor(private readonly launcherService: UiLauncherService) {} get status(): PackageMainStatus { - return this.pkg.entry.installed?.status.main.status; + return this.pkg.entry.installed?.status.main.status } get manifest(): Manifest { - return this.pkg.entry.manifest; + return this.pkg.entry.manifest } - launchUi(pkg: PackageDataEntry): void { - this.document.defaultView.open( - this.config.launchableURL(pkg), - "_blank", - "noreferrer" - ); + launchUi(): void { + this.launcherService.launch(this.pkg.entry) } } diff --git a/ui/src/app/pages/apps-routes/app-show/app-show.module.ts b/ui/src/app/pages/apps-routes/app-show/app-show.module.ts index 90f3e1f30..29c14ce6e 100644 --- a/ui/src/app/pages/apps-routes/app-show/app-show.module.ts +++ b/ui/src/app/pages/apps-routes/app-show/app-show.module.ts @@ -2,12 +2,23 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' import { Routes, RouterModule } from '@angular/router' import { IonicModule } from '@ionic/angular' -import { AppShowPage, HealthColorPipe } from './app-show.page' +import { AppShowPage } from './app-show.page' import { StatusComponentModule } from 'src/app/components/status/status.component.module' import { SharingModule } from 'src/app/modules/sharing.module' import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.component.module' import { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module' import { MarkdownPageModule } from 'src/app/modals/markdown/markdown.module' +import { AppShowHeaderComponent } from './components/app-show-header/app-show-header.component' +import { AppShowProgressComponent } from './components/app-show-progress/app-show-progress.component' +import { AppShowStatusComponent } from './components/app-show-status/app-show-status.component' +import { AppShowDependenciesComponent } from './components/app-show-dependencies/app-show-dependencies.component' +import { AppShowMenuComponent } from './components/app-show-menu/app-show-menu.component' +import { AppShowHealthChecksComponent } from './components/app-show-health-checks/app-show-health-checks.component' +import { HealthColorPipe } from './pipes/health-color.pipe' +import { ToHealthChecksPipe } from './pipes/to-health-checks.pipe' +import { ToButtonsPipe } from './pipes/to-buttons.pipe' +import { ToDependenciesPipe } from './pipes/to-dependencies.pipe' +import { ToStatusPipe } from './pipes/to-status.pipe' const routes: Routes = [ { @@ -20,6 +31,16 @@ const routes: Routes = [ declarations: [ AppShowPage, HealthColorPipe, + ToHealthChecksPipe, + ToButtonsPipe, + ToDependenciesPipe, + ToStatusPipe, + AppShowHeaderComponent, + AppShowProgressComponent, + AppShowStatusComponent, + AppShowDependenciesComponent, + AppShowMenuComponent, + AppShowHealthChecksComponent, ], imports: [ CommonModule, @@ -32,5 +53,4 @@ const routes: Routes = [ MarkdownPageModule, ], }) -export class AppShowPageModule { } - +export class AppShowPageModule {} diff --git a/ui/src/app/pages/apps-routes/app-show/app-show.page.html b/ui/src/app/pages/apps-routes/app-show/app-show.page.html index 3f2020e6e..cb10e2f0f 100644 --- a/ui/src/app/pages/apps-routes/app-show/app-show.page.html +++ b/ui/src/app/pages/apps-routes/app-show/app-show.page.html @@ -1,158 +1,40 @@ - - - - - - - - - - -

- {{ pkg.manifest.title }} -

-

{{ pkg.manifest.version | displayEmver }}

-
-
-
-
+ + - - - - Status - - - - - - - Launch UI - - - - Configure - - - Stop - - - Start - - - - - - - + + + + + + - - Health Checks - - - - - - - - - - - - - - - - - - - -

{{ pkg.manifest['health-checks'][health.key].name }}

- -

- {{ result | titlecase }} - ... - {{ $any(health.value).error }} - {{ $any(health.value).message }} -

-
-
-
- - - -

{{ health.key }}

-

Awaiting result...

-
-
-
-
-
+ - - Dependencies - - - - - - -

- - {{ dep.title }} -

-

{{ dep.version | displayEmver }}

-

- {{ dep.errorText || 'satisfied' }} -

-
- - {{ dep.actionText }} - - -
-
+ - Menu - - - -

{{ button.title }}

-

{{ button.description }}

-
-
+
+
+ + + + -
- - -
-

Downloading: {{ installProgress.downloadProgress }}%

- - -

Validating: {{ installProgress.validateProgress }}%

- - -

Unpacking: {{ installProgress.unpackProgress }}%

- -
-
+ +
diff --git a/ui/src/app/pages/apps-routes/app-show/app-show.page.scss b/ui/src/app/pages/apps-routes/app-show/app-show.page.scss deleted file mode 100644 index 7ec7d334e..000000000 --- a/ui/src/app/pages/apps-routes/app-show/app-show.page.scss +++ /dev/null @@ -1,14 +0,0 @@ -.less-large { - font-size: 18px !important; -} - -.action-button { - margin: 10px; - min-height: 36px; - min-width: 120px; -} - -.icon-spinner { - height: 20px; - width: 20px; -} \ No newline at end of file diff --git a/ui/src/app/pages/apps-routes/app-show/app-show.page.ts b/ui/src/app/pages/apps-routes/app-show/app-show.page.ts index daa6b6fdb..406c02913 100644 --- a/ui/src/app/pages/apps-routes/app-show/app-show.page.ts +++ b/ui/src/app/pages/apps-routes/app-show/app-show.page.ts @@ -1,469 +1,73 @@ -import { Component, ViewChild } from '@angular/core' -import { AlertController, NavController, ModalController, IonContent, LoadingController } from '@ionic/angular' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { ActivatedRoute, NavigationExtras } from '@angular/router' -import { DependentInfo, exists, isEmptyObject } from 'src/app/util/misc.util' -import { combineLatest, Subscription } from 'rxjs' -import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component' -import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards' -import { ConfigService } from 'src/app/services/config.service' +import { ChangeDetectionStrategy, Component } from '@angular/core' +import { NavController } from '@ionic/angular' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { DependencyError, DependencyErrorType, HealthCheckResult, HealthResult, PackageDataEntry, PackageMainStatus, PackageState } from 'src/app/services/patch-db/data-model' -import { DependencyStatus, HealthStatus, PrimaryRendering, PrimaryStatus, renderPkgStatus } from 'src/app/services/pkg-status-rendering.service' -import { ConnectionFailure, ConnectionService } from 'src/app/services/connection.service' -import { ErrorToastService } from 'src/app/services/error-toast.service' -import { AppConfigPage } from 'src/app/modals/app-config/app-config.page' -import { filter } from 'rxjs/operators' -import { MarkdownPage } from 'src/app/modals/markdown/markdown.page' -import { Pipe, PipeTransform } from '@angular/core' -import { packageLoadingProgress, ProgressData } from 'src/app/util/package-loading-progress' +import { + PackageDataEntry, + PackageState, +} from 'src/app/services/patch-db/data-model' +import { + PackageStatus, + PrimaryStatus, +} from 'src/app/services/pkg-status-rendering.service' +import { + ConnectionFailure, + ConnectionService, +} from 'src/app/services/connection.service' +import { map, startWith } from 'rxjs/operators' +import { ActivatedRoute } from '@angular/router' + +const STATES = [ + PackageState.Installing, + PackageState.Updating, + PackageState.Restoring, +] @Component({ selector: 'app-show', templateUrl: './app-show.page.html', - styleUrls: ['./app-show.page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppShowPage { - PackageState = PackageState - DependencyErrorType = DependencyErrorType - Math = Math - HealthResult = HealthResult - PS = PrimaryStatus - DS = DependencyStatus - PR = PrimaryRendering + private readonly pkgId = this.route.snapshot.paramMap.get('pkgId') - pkgId: string - pkg: PackageDataEntry - hideLAN: boolean - buttons: Button[] = [] - dependencies: DependencyInfo[] = [] - statuses: { - primary: PrimaryStatus - dependency: DependencyStatus - health: HealthStatus - } = { } as any - connectionFailure: boolean - loading = true - healthChecks: { [id: string]: HealthCheckResult | null } - installProgress: ProgressData + readonly pkg$ = this.patch.watch$('package-data', this.pkgId).pipe( + map(pkg => { + // if package disappears, navigate to list page + if (!pkg) { + this.navCtrl.navigateRoot('/services') + } - @ViewChild(IonContent) content: IonContent - subs: Subscription[] = [] + return { ...pkg } + }), + startWith(this.patch.getData()['package-data'][this.pkgId]), + ) - constructor ( - private readonly alertCtrl: AlertController, + readonly connectionFailure$ = this.connectionService + .watchFailure$() + .pipe(map(failure => failure !== ConnectionFailure.None)) + + constructor( private readonly route: ActivatedRoute, private readonly navCtrl: NavController, - private readonly errToast: ErrorToastService, - private readonly loadingCtrl: LoadingController, - private readonly modalCtrl: ModalController, - private readonly embassyApi: ApiService, - private readonly wizardBaker: WizardBaker, - private readonly config: ConfigService, private readonly patch: PatchDbService, private readonly connectionService: ConnectionService, - ) { } + ) {} - async ngOnInit () { - this.pkgId = this.route.snapshot.paramMap.get('pkgId') - this.pkg = this.patch.getData()['package-data'][this.pkgId] - this.statuses = renderPkgStatus(this.pkg) - this.healthChecks = Object.keys(this.pkg.manifest['health-checks']).reduce((obj, key) => { - obj[key] = null - return obj - }, { }) - - this.subs = [ - // 1 - this.patch.watch$('package-data', this.pkgId) - .subscribe(pkg => { - // if package disappears, navigate to list page - if (!pkg) { - this.navCtrl.navigateRoot('/services') - return - } - - this.pkg = pkg - this.statuses = renderPkgStatus(pkg) - this.installProgress = !isEmptyObject(pkg['install-progress']) ? packageLoadingProgress(pkg['install-progress']) : undefined - }), - - // 2 - combineLatest([ - this.patch.watch$('package-data', this.pkgId, 'installed', 'current-dependencies'), - this.patch.watch$('package-data', this.pkgId, 'installed', 'status', 'dependency-errors'), - ]) - .pipe( - filter(([currentDeps, depErrors]) => exists(currentDeps) && exists(depErrors)), - ) - .subscribe(([currentDeps, depErrors]) => { - this.dependencies = Object.keys(currentDeps) - .filter(id => !!this.pkg.manifest.dependencies[id]) - .map(id => { - return this.setDepValues(id, depErrors) - }) - }), - - // 3 - this.patch.watch$('package-data', this.pkgId, 'installed', 'status', 'main') - .pipe( - filter(obj => exists(obj)), - ) - .subscribe(main => { - if (main.status === PackageMainStatus.Running) { - Object.keys(this.healthChecks).forEach(key => { - this.healthChecks[key] = main.health[key] - }) - } else { - Object.keys(this.healthChecks).forEach(key => { - this.healthChecks[key] = null - }) - } - }), - - // 4 - this.connectionService.watchFailure$() - .subscribe(connectionFailure => { - this.connectionFailure = connectionFailure !== ConnectionFailure.None - }), - ] - this.setButtons() + isInstalled( + { state }: PackageDataEntry, + { primary }: PackageStatus, + ): boolean { + return ( + state === PackageState.Installed && primary !== PrimaryStatus.BackingUp + ) } - ngAfterViewInit () { - this.content.scrollToPoint(undefined, 1) + isRunning({ primary }: PackageStatus): boolean { + return primary === PrimaryStatus.Running } - ngOnDestroy () { - this.subs.forEach(sub => sub.unsubscribe()) - } - - launchUi (): void { - window.open(this.config.launchableURL(this.pkg), '_blank', 'noreferrer') - } - - async stop (): Promise { - const { id, title, version } = this.pkg.manifest - - if (isEmptyObject(this.pkg.installed['current-dependents'])) { - const loader = await this.loadingCtrl.create({ - message: `Stopping...`, - spinner: 'lines', - cssClass: 'loader', - }) - await loader.present() - - try { - await this.embassyApi.stopPackage({ id }) - } catch (e) { - this.errToast.present(e) - } finally { - loader.dismiss() - } - } else { - wizardModal( - this.modalCtrl, - this.wizardBaker.stop({ - id, - title, - version, - }), - ) - } - } - - async tryStart (): Promise { - if (this.dependencies.some(d => !!d.errorText)) { - const depErrMsg = `${this.pkg.manifest.title} has unmet dependencies. It will not work as expected.` - const proceed = await this.presentAlertStart(depErrMsg) - if (!proceed) return - } - - const alertMsg = this.pkg.manifest.alerts.start - if (!!alertMsg) { - const proceed = await this.presentAlertStart(alertMsg) - if (!proceed) return - } - - this.start() - } - - async donate (): Promise { - const url = this.pkg.manifest['donation-url'] - if (url) { - window.open(url, '_blank', 'noreferrer') - } else { - const alert = await this.alertCtrl.create({ - header: 'Not Accepting Donations', - message: `The developers of ${this.pkg.manifest.title} have not provided a donation URL. Please contact them directly if you insist on giving them money.`, - }) - await alert.present() - } - } - - async fixDep (action: 'install' | 'update' | 'configure', id: string): Promise { - switch (action) { - case 'install': - case 'update': - return this.installDep(id) - case 'configure': - return this.configureDep(id) - } - } - - async presentModalConfig (props: { pkgId: string, dependentInfo?: DependentInfo }): Promise { - const modal = await this.modalCtrl.create({ - component: AppConfigPage, - componentProps: props, - }) - await modal.present() - } - - async presentModalInstructions () { - const modal = await this.modalCtrl.create({ - componentProps: { - title: 'Instructions', - contentUrl: this.pkg['static-files']['instructions'], - }, - component: MarkdownPage, - }) - - await modal.present() - } - - async presentAlertDescription (id: string) { - const health = this.pkg.manifest['health-checks'][id] - - const alert = await this.alertCtrl.create({ - header: 'Health Check', - subHeader: health.name, - message: health.description, - buttons: [ - { - text: `OK`, - handler: () => { - alert.dismiss() - }, - cssClass: 'enter-click', - }, - ], - }) - await alert.present() - } - - private setDepValues (id: string, errors: { [id: string]: DependencyError }): DependencyInfo { - let errorText = '' - let actionText = 'View' - let action: () => any = () => this.navCtrl.navigateForward(`/services/${id}`) - - const error = errors[id] - - if (error) { - // health checks failed - if ([DependencyErrorType.InterfaceHealthChecksFailed, DependencyErrorType.HealthChecksFailed].includes(error.type)) { - errorText = 'Health check failed' - // not installed - } else if (error.type === DependencyErrorType.NotInstalled) { - errorText = 'Not installed' - actionText = 'Install' - action = () => this.fixDep('install', id) - // incorrect version - } else if (error.type === DependencyErrorType.IncorrectVersion) { - errorText = 'Incorrect version' - actionText = 'Update' - action = () => this.fixDep('update', id) - // not running - } else if (error.type === DependencyErrorType.NotRunning) { - errorText = 'Not running' - actionText = 'Start' - // config unsatisfied - } else if (error.type === DependencyErrorType.ConfigUnsatisfied) { - errorText = 'Config not satisfied' - actionText = 'Auto config' - action = () => this.fixDep('configure', id) - } else if (error.type === DependencyErrorType.Transitive) { - errorText = 'Dependency has a dependency issue' - } - errorText = `${errorText}. ${ this.pkg.manifest.title} will not work as expected.` - } - - const depInfo = this.pkg.installed['dependency-info'][id] - - return { - id, - version: this.pkg.manifest.dependencies[id].version, - title: depInfo.manifest.title, - icon: depInfo.icon, - errorText, - actionText, - action, - } - } - - private async installDep (depId: string): Promise { - const version = this.pkg.manifest.dependencies[depId].version - - const dependentInfo: DependentInfo = { - id: this.pkgId, - title: this.pkg.manifest.title, - version, - } - const navigationExtras: NavigationExtras = { - state: { dependentInfo }, - } - - await this.navCtrl.navigateForward(`/marketplace/${depId}`, navigationExtras) - } - - private async configureDep (dependencyId: string): Promise { - const dependentInfo: DependentInfo = { - id: this.pkgId, - title: this.pkg.manifest.title, - } - - await this.presentModalConfig({ - pkgId: dependencyId, - dependentInfo, - }) - } - - private async presentAlertStart (message: string): Promise { - return new Promise(async resolve => { - const alert = await this.alertCtrl.create({ - header: 'Warning', - message, - buttons: [ - { - text: 'Cancel', - role: 'cancel', - handler: () => { - resolve(false) - }, - }, - { - text: 'Continue', - handler: () => { - resolve(true) - }, - cssClass: 'enter-click', - }, - ], - }) - await alert.present() - }) - } - - private async start (): Promise { - const loader = await this.loadingCtrl.create({ - message: `Starting...`, - spinner: 'lines', - cssClass: 'loader', - }) - await loader.present() - - try { - await this.embassyApi.startPackage({ id: this.pkgId }) - } catch (e) { - this.errToast.present(e) - } finally { - loader.dismiss() - } - } - - private setButtons (): void { - const pkgTitle = this.pkg.manifest.title - - this.buttons = [ - // instructions - { - action: () => this.presentModalInstructions(), - title: 'Instructions', - description: `Understand how to use ${pkgTitle}`, - icon: 'list-outline', - }, - // config - { - action: async () => this.presentModalConfig({ pkgId: this.pkgId }), - title: 'Config', - description: `Customize ${pkgTitle}`, - icon: 'construct-outline', - }, - // properties - { - action: () => this.navCtrl.navigateForward(['properties'], { relativeTo: this.route }), - title: 'Properties', - description: 'Runtime information, credentials, and other values of interest', - icon: 'briefcase-outline', - }, - // actions - { - action: () => this.navCtrl.navigateForward(['actions'], { relativeTo: this.route }), - title: 'Actions', - description: `Uninstall and other commands specific to ${pkgTitle}`, - icon: 'flash-outline', - }, - // interfaces - { - action: () => this.navCtrl.navigateForward(['interfaces'], { relativeTo: this.route }), - title: 'Interfaces', - description: 'User and machine access points', - icon: 'desktop-outline', - }, - { - action: () => this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }), - title: 'Logs', - description: 'Raw, unfiltered service logs', - icon: 'receipt-outline', - }, - { - action: () => this.navCtrl.navigateForward([`marketplace/${this.pkgId}`]), - title: 'Marketplace', - description: 'View service in marketplace', - icon: 'storefront-outline', - }, - { - action: () => this.donate(), - title: 'Donate', - description: `Support ${pkgTitle}`, - icon: 'logo-bitcoin', - }, - ] - } - - asIsOrder () { - return 0 + showProgress({ state }: PackageDataEntry): boolean { + return STATES.includes(state) } } -interface DependencyInfo { - id: string - title: string - icon: string - version: string - errorText: string - actionText: string - action: () => any -} - -interface Button { - title: string - description: string - icon: string - action: Function -} - - -@Pipe({ - name: 'healthColor', -}) -export class HealthColorPipe implements PipeTransform { - transform (val: HealthResult): string { - switch (val) { - case HealthResult.Success: return 'success' - case HealthResult.Failure: return 'warning' - case HealthResult.Disabled: return 'dark' - case HealthResult.Starting: - case HealthResult.Loading: return 'primary' - } - } -} diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.html b/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.html new file mode 100644 index 000000000..13e79486e --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.html @@ -0,0 +1,29 @@ +Dependencies + + + + + + +

+ + {{ dep.title }} +

+

{{ dep.version | displayEmver }}

+

+ + {{ dep.errorText || 'satisfied' }} + +

+
+ + {{ dep.actionText }} + + +
diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.scss b/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.scss new file mode 100644 index 000000000..6b7876259 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.scss @@ -0,0 +1,7 @@ +.inline { + font-family: 'Montserrat', sans-serif; +} + +.icon { + padding-right: 4px; +} diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.ts b/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.ts new file mode 100644 index 000000000..26d4cd026 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-dependencies/app-show-dependencies.component.ts @@ -0,0 +1,13 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { DependencyInfo } from '../../pipes/to-dependencies.pipe' + +@Component({ + selector: 'app-show-dependencies', + templateUrl: './app-show-dependencies.component.html', + styleUrls: ['./app-show-dependencies.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppShowDependenciesComponent { + @Input() + dependencies: DependencyInfo[] = [] +} diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.html b/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.html new file mode 100644 index 000000000..1745d2457 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.html @@ -0,0 +1,18 @@ + + + + + + + + + + +

+ {{ pkg.manifest.title }} +

+

{{ pkg.manifest.version | displayEmver }}

+
+
+
+
diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.scss b/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.scss new file mode 100644 index 000000000..fff277abd --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.scss @@ -0,0 +1,7 @@ +.name { + font-family: 'Montserrat', sans-serif; +} + +.less-large { + font-size: 18px !important; +} diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.ts b/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.ts new file mode 100644 index 000000000..3dbf07e0e --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.ts @@ -0,0 +1,13 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { PackageDataEntry } from 'src/app/services/patch-db/data-model' + +@Component({ + selector: 'app-show-header', + templateUrl: './app-show-header.component.html', + styleUrls: ['./app-show-header.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppShowHeaderComponent { + @Input() + pkg: PackageDataEntry +} diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.html b/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.html new file mode 100644 index 000000000..8a42851be --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.html @@ -0,0 +1,78 @@ + + Health Checks + + + + + + + + + + + + + + + + + + + +

+ {{ pkg.manifest['health-checks'][health.key].name }} +

+ +

+ {{ result | titlecase }} + ... + + {{ $any(health.value).error }} + + + {{ $any(health.value).message }} + +

+
+
+
+ + + +

{{ health.key }}

+

Awaiting result...

+
+
+
+
+
diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.scss b/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.scss new file mode 100644 index 000000000..db58b129d --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.scss @@ -0,0 +1,24 @@ +.icon-spinner { + height: 20px; + width: 20px; +} + +.avatar { + width: 20px; + height: 20px; + border-radius: 0; +} + +.label { + width: 100px; + margin-bottom: 10px; +} + +.description { + width: 150px; + margin-bottom: 10px; +} + +.bold { + font-weight: bold; +} diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.ts b/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.ts new file mode 100644 index 000000000..9655c29d5 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.ts @@ -0,0 +1,56 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { AlertController } from '@ionic/angular' +import { + HealthResult, + PackageDataEntry, +} from 'src/app/services/patch-db/data-model' + +@Component({ + selector: 'app-show-health-checks', + templateUrl: './app-show-health-checks.component.html', + styleUrls: ['./app-show-health-checks.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppShowHealthChecksComponent { + @Input() + pkg: PackageDataEntry + + @Input() + connectionFailure = false + + HealthResult = HealthResult + + constructor(private readonly alertCtrl: AlertController) {} + + isLoading(result: HealthResult): boolean { + return result === HealthResult.Starting || result === HealthResult.Loading + } + + isReady(result: HealthResult): boolean { + return result !== HealthResult.Failure && result !== HealthResult.Loading + } + + async presentAlertDescription(id: string) { + const health = this.pkg.manifest['health-checks'][id] + + const alert = await this.alertCtrl.create({ + header: 'Health Check', + subHeader: health.name, + message: health.description, + buttons: [ + { + text: `OK`, + handler: () => { + alert.dismiss() + }, + cssClass: 'enter-click', + }, + ], + }) + await alert.present() + } + + asIsOrder() { + return 0 + } +} diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-menu/app-show-menu.component.html b/ui/src/app/pages/apps-routes/app-show/components/app-show-menu/app-show-menu.component.html new file mode 100644 index 000000000..d0537b481 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-menu/app-show-menu.component.html @@ -0,0 +1,13 @@ +Menu + + + +

{{ button.title }}

+

{{ button.description }}

+
+
diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-menu/app-show-menu.component.ts b/ui/src/app/pages/apps-routes/app-show/components/app-show-menu/app-show-menu.component.ts new file mode 100644 index 000000000..39d16b236 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-menu/app-show-menu.component.ts @@ -0,0 +1,12 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { Button } from '../../pipes/to-buttons.pipe' + +@Component({ + selector: 'app-show-menu', + templateUrl: './app-show-menu.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppShowMenuComponent { + @Input() + buttons: Button[] = [] +} diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.html b/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.html new file mode 100644 index 000000000..8365f7345 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.html @@ -0,0 +1,20 @@ +

Downloading: {{ installProgress.downloadProgress }}%

+ + +

Validating: {{ installProgress.validateProgress }}%

+ + +

Unpacking: {{ installProgress.unpackProgress }}%

+ diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.scss b/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.scss new file mode 100644 index 000000000..7ffd8ed70 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.scss @@ -0,0 +1,4 @@ +:host { + display: block; + padding: 16px; +} diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.ts b/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.ts new file mode 100644 index 000000000..750ee90b9 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.ts @@ -0,0 +1,38 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { + InstallProgress, + PackageDataEntry, +} from 'src/app/services/patch-db/data-model' +import { ProgressData } from 'src/app/util/package-loading-progress' + +@Component({ + selector: 'app-show-progress', + templateUrl: './app-show-progress.component.html', + styleUrls: ['./app-show-progress.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppShowProgressComponent { + @Input() + pkg: PackageDataEntry + + @Input() + installProgress: ProgressData + + get unpackingBuffer(): number { + return this.installProgress.validateProgress === 100 && + !this.installProgress.unpackProgress + ? 0 + : 1 + } + + get validationBuffer(): number { + return this.installProgress.downloadProgress === 100 && + !this.installProgress.validateProgress + ? 0 + : 1 + } + + getColor(action: keyof InstallProgress): string { + return this.pkg['install-progress'][action] ? 'success' : 'secondary' + } +} diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html b/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html new file mode 100644 index 000000000..421fc493d --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html @@ -0,0 +1,51 @@ +Status + + + + + + + Launch UI + + + + Configure + + + Stop + + + Start + + + diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.scss b/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.scss new file mode 100644 index 000000000..5c072aa89 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.scss @@ -0,0 +1,9 @@ +.label { + overflow: visible; +} + +.action-button { + margin: 10px; + min-height: 36px; + min-width: 120px; +} diff --git a/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts b/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts new file mode 100644 index 000000000..e0a6adf94 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.ts @@ -0,0 +1,193 @@ +import { + ChangeDetectionStrategy, + Component, + Inject, + Input, +} from '@angular/core' +import { UiLauncherService } from 'src/app/services/ui-launcher.service' +import { + InstallProgress, + InterfaceDef, + PackageDataEntry, + PackageState, + Status, +} from 'src/app/services/patch-db/data-model' +import { + PackageStatus, + PrimaryRendering, + PrimaryStatus, +} from 'src/app/services/pkg-status-rendering.service' +import { isEmptyObject } from 'src/app/util/misc.util' +import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component' +import { + AlertController, + LoadingController, + ModalController, +} from '@ionic/angular' +import { ErrorToastService } from 'src/app/services/error-toast.service' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards' +import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { ModalService } from 'src/app/services/modal.service' +import { DependencyInfo } from '../../pipes/to-dependencies.pipe' + +@Component({ + selector: 'app-show-status', + templateUrl: './app-show-status.component.html', + styleUrls: ['./app-show-status.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppShowStatusComponent { + @Input() + pkg: PackageDataEntry + + @Input() + connectionFailure = false + + @Input() + status: PackageStatus + + @Input() + dependencies: DependencyInfo[] = [] + + PR = PrimaryRendering + + constructor( + private readonly alertCtrl: AlertController, + private readonly errToast: ErrorToastService, + private readonly loadingCtrl: LoadingController, + private readonly modalCtrl: ModalController, + private readonly embassyApi: ApiService, + private readonly wizardBaker: WizardBaker, + private readonly patch: PatchDbService, + private readonly launcherService: UiLauncherService, + private readonly modalService: ModalService, + ) {} + + get interfaces(): Record { + return this.pkg.manifest.interfaces + } + + get progress(): InstallProgress | undefined { + return this.pkg['install-progress'] + } + + get pkgStatus(): Status { + return this.pkg.installed.status + } + + get isInstalled(): boolean { + return this.pkg.state === PackageState.Installed && !this.connectionFailure + } + + get isRunning(): boolean { + return this.status.primary === PrimaryStatus.Running + } + + get isStopped(): boolean { + return ( + this.status.primary === PrimaryStatus.Stopped && this.pkgStatus.configured + ) + } + + launchUi(): void { + this.launcherService.launch(this.pkg) + } + + async presentModalConfig(): Promise { + return this.modalService.presentModalConfig({ pkgId: this.pkg.manifest.id }) + } + + async tryStart(): Promise { + if (this.dependencies.some(d => !!d.errorText)) { + const depErrMsg = `${this.pkg.manifest.title} has unmet dependencies. It will not work as expected.` + const proceed = await this.presentAlertStart(depErrMsg) + + if (!proceed) return + } + + const alertMsg = this.pkg.manifest.alerts.start + + if (!!alertMsg) { + const proceed = await this.presentAlertStart(alertMsg) + + if (!proceed) return + } + + this.start() + } + + async stop(): Promise { + const { id, title, version } = this.pkg.manifest + + if (isEmptyObject(this.pkg.installed['current-dependents'])) { + const loader = await this.loadingCtrl.create({ + message: `Stopping...`, + spinner: 'lines', + cssClass: 'loader', + }) + await loader.present() + + try { + await this.embassyApi.stopPackage({ id }) + } catch (e) { + this.errToast.present(e) + } finally { + loader.dismiss() + } + } else { + wizardModal( + this.modalCtrl, + this.wizardBaker.stop({ + id, + title, + version, + }), + ) + } + } + + private async start(): Promise { + const loader = await this.loadingCtrl.create({ + message: `Starting...`, + spinner: 'lines', + cssClass: 'loader', + }) + await loader.present() + + try { + await this.embassyApi.startPackage({ id: this.pkg.manifest.id }) + } catch (e) { + this.errToast.present(e) + } finally { + loader.dismiss() + } + } + + private async presentAlertStart(message: string): Promise { + return new Promise(async resolve => { + const alert = await this.alertCtrl.create({ + header: 'Warning', + message, + buttons: [ + { + text: 'Cancel', + role: 'cancel', + handler: () => { + resolve(false) + }, + }, + { + text: 'Continue', + handler: () => { + resolve(true) + }, + cssClass: 'enter-click', + }, + ], + }) + + await alert.present() + }) + } +} diff --git a/ui/src/app/pages/apps-routes/app-show/pipes/health-color.pipe.ts b/ui/src/app/pages/apps-routes/app-show/pipes/health-color.pipe.ts new file mode 100644 index 000000000..a274aa8c0 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/pipes/health-color.pipe.ts @@ -0,0 +1,21 @@ +import { Pipe, PipeTransform } from '@angular/core' +import { HealthResult } from 'src/app/services/patch-db/data-model' + +@Pipe({ + name: 'healthColor', +}) +export class HealthColorPipe implements PipeTransform { + transform(val: HealthResult): string { + switch (val) { + case HealthResult.Success: + return 'success' + case HealthResult.Failure: + return 'warning' + case HealthResult.Disabled: + return 'dark' + case HealthResult.Starting: + case HealthResult.Loading: + return 'primary' + } + } +} diff --git a/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts b/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts new file mode 100644 index 000000000..dfc46b127 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts @@ -0,0 +1,125 @@ +import { Inject, Pipe, PipeTransform } from '@angular/core' +import { ActivatedRoute } from '@angular/router' +import { DOCUMENT } from '@angular/common' +import { AlertController, ModalController, NavController } from '@ionic/angular' +import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { MarkdownPage } from 'src/app/modals/markdown/markdown.page' +import { ModalService } from 'src/app/services/modal.service' + +export interface Button { + title: string + description: string + icon: string + action: Function +} + +@Pipe({ + name: 'toButtons', +}) +export class ToButtonsPipe implements PipeTransform { + constructor( + @Inject(DOCUMENT) private readonly document: Document, + private readonly alertCtrl: AlertController, + private readonly route: ActivatedRoute, + private readonly navCtrl: NavController, + private readonly modalCtrl: ModalController, + private readonly modalService: ModalService, + ) {} + + transform(pkg: PackageDataEntry): Button[] { + const pkgTitle = pkg.manifest.title + + return [ + // instructions + { + action: () => this.presentModalInstructions(pkg), + title: 'Instructions', + description: `Understand how to use ${pkgTitle}`, + icon: 'list-outline', + }, + // config + { + action: async () => + this.modalService.presentModalConfig({ pkgId: pkg.manifest.id }), + title: 'Config', + description: `Customize ${pkgTitle}`, + icon: 'construct-outline', + }, + // properties + { + action: () => + this.navCtrl.navigateForward(['properties'], { + relativeTo: this.route, + }), + title: 'Properties', + description: + 'Runtime information, credentials, and other values of interest', + icon: 'briefcase-outline', + }, + // actions + { + action: () => + this.navCtrl.navigateForward(['actions'], { relativeTo: this.route }), + title: 'Actions', + description: `Uninstall and other commands specific to ${pkgTitle}`, + icon: 'flash-outline', + }, + // interfaces + { + action: () => + this.navCtrl.navigateForward(['interfaces'], { + relativeTo: this.route, + }), + title: 'Interfaces', + description: 'User and machine access points', + icon: 'desktop-outline', + }, + // logs + { + action: () => + this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }), + title: 'Logs', + description: 'Raw, unfiltered service logs', + icon: 'receipt-outline', + }, + // view in marketplace + { + action: () => this.navCtrl.navigateForward([`marketplace/${pkg.manifest.id}`]), + title: 'Marketplace', + description: 'View service in marketplace', + icon: 'storefront-outline', + }, + { + action: () => this.donate(pkg), + title: 'Donate', + description: `Support ${pkgTitle}`, + icon: 'logo-bitcoin', + }, + ] + } + + private async presentModalInstructions(pkg: PackageDataEntry) { + const modal = await this.modalCtrl.create({ + componentProps: { + title: 'Instructions', + contentUrl: pkg['static-files']['instructions'], + }, + component: MarkdownPage, + }) + + await modal.present() + } + + private async donate({ manifest }: PackageDataEntry): Promise { + const url = manifest['donation-url'] + if (url) { + this.document.defaultView.open(url, '_blank', 'noreferrer') + } else { + const alert = await this.alertCtrl.create({ + header: 'Not Accepting Donations', + message: `The developers of ${manifest.title} have not provided a donation URL. Please contact them directly if you insist on giving them money.`, + }) + await alert.present() + } + } +} diff --git a/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts b/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts new file mode 100644 index 000000000..44d400831 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/pipes/to-dependencies.pipe.ts @@ -0,0 +1,168 @@ +import { Pipe, PipeTransform } from '@angular/core' +import { NavigationExtras } from '@angular/router' +import { NavController } from '@ionic/angular' +import { combineLatest, Observable } from 'rxjs' +import { filter, map } from 'rxjs/operators' +import { DependentInfo, exists } from 'src/app/util/misc.util' +import { + DependencyError, + DependencyErrorType, + PackageDataEntry, +} from 'src/app/services/patch-db/data-model' +import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { ModalService } from 'src/app/services/modal.service' + +export interface DependencyInfo { + id: string + title: string + icon: string + version: string + errorText: string + actionText: string + action: () => any +} + +@Pipe({ + name: 'toDependencies', +}) +export class ToDependenciesPipe implements PipeTransform { + constructor( + private readonly patch: PatchDbService, + private readonly navCtrl: NavController, + private readonly modalService: ModalService, + ) {} + + transform(pkg: PackageDataEntry): Observable { + return combineLatest([ + this.patch.watch$( + 'package-data', + pkg.manifest.id, + 'installed', + 'current-dependencies', + ), + this.patch.watch$( + 'package-data', + pkg.manifest.id, + 'installed', + 'status', + 'dependency-errors', + ), + ]).pipe( + filter(deps => deps.every(exists)), + map(([currentDeps, depErrors]) => + Object.keys(currentDeps) + .filter(id => !!pkg.manifest.dependencies[id]) + .map(id => this.setDepValues(pkg, id, depErrors)), + ), + ) + } + + private setDepValues( + pkg: PackageDataEntry, + id: string, + errors: { [id: string]: DependencyError }, + ): DependencyInfo { + let errorText = '' + let actionText = 'View' + let action: () => any = () => + this.navCtrl.navigateForward(`/services/${id}`) + + const error = errors[id] + + if (error) { + // health checks failed + if ( + [ + DependencyErrorType.InterfaceHealthChecksFailed, + DependencyErrorType.HealthChecksFailed, + ].includes(error.type) + ) { + errorText = 'Health check failed' + // not installed + } else if (error.type === DependencyErrorType.NotInstalled) { + errorText = 'Not installed' + actionText = 'Install' + action = () => this.fixDep(pkg, 'install', id) + // incorrect version + } else if (error.type === DependencyErrorType.IncorrectVersion) { + errorText = 'Incorrect version' + actionText = 'Update' + action = () => this.fixDep(pkg, 'update', id) + // not running + } else if (error.type === DependencyErrorType.NotRunning) { + errorText = 'Not running' + actionText = 'Start' + // config unsatisfied + } else if (error.type === DependencyErrorType.ConfigUnsatisfied) { + errorText = 'Config not satisfied' + actionText = 'Auto config' + action = () => this.fixDep(pkg, 'configure', id) + } else if (error.type === DependencyErrorType.Transitive) { + errorText = 'Dependency has a dependency issue' + } + errorText = `${errorText}. ${pkg.manifest.title} will not work as expected.` + } + + const depInfo = pkg.installed['dependency-info'][id] + + return { + id, + version: pkg.manifest.dependencies[id].version, + title: depInfo.manifest.title, + icon: depInfo.icon, + errorText, + actionText, + action, + } + } + + async fixDep( + pkg: PackageDataEntry, + action: 'install' | 'update' | 'configure', + id: string, + ): Promise { + switch (action) { + case 'install': + case 'update': + return this.installDep(pkg, id) + case 'configure': + return this.configureDep(pkg, id) + } + } + + private async installDep( + pkg: PackageDataEntry, + depId: string, + ): Promise { + const version = pkg.manifest.dependencies[depId].version + + const dependentInfo: DependentInfo = { + id: pkg.manifest.id, + title: pkg.manifest.title, + version, + } + const navigationExtras: NavigationExtras = { + state: { dependentInfo }, + } + + await this.navCtrl.navigateForward( + `/marketplace/${depId}`, + navigationExtras, + ) + } + + private async configureDep( + pkg: PackageDataEntry, + dependencyId: string, + ): Promise { + const dependentInfo: DependentInfo = { + id: pkg.manifest.id, + title: pkg.manifest.title, + } + + await this.modalService.presentModalConfig({ + pkgId: dependencyId, + dependentInfo, + }) + } +} diff --git a/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts b/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts new file mode 100644 index 000000000..459ef32aa --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/pipes/to-health-checks.pipe.ts @@ -0,0 +1,42 @@ +import { Inject, Pipe, PipeTransform } from '@angular/core' +import { + HealthCheckResult, + PackageDataEntry, + PackageMainStatus, +} from 'src/app/services/patch-db/data-model' +import { exists, isEmptyObject } from 'src/app/util/misc.util' +import { filter, map, startWith } from 'rxjs/operators' +import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { Observable } from 'rxjs' + +@Pipe({ + name: 'toHealthChecks', +}) +export class ToHealthChecksPipe implements PipeTransform { + constructor(private readonly patch: PatchDbService) {} + + transform( + pkg: PackageDataEntry, + ): Observable> | null { + const healthChecks = Object.keys(pkg.manifest['health-checks']).reduce( + (obj, key) => ({ ...obj, [key]: null }), + {}, + ) + + const healthChecks$ = this.patch + .watch$('package-data', pkg.manifest.id, 'installed', 'status', 'main') + .pipe( + filter(obj => exists(obj)), + map(main => + // Question: is this ok or do we have to use Object.keys + // to maintain order and the keys initially present in pkg? + main.status === PackageMainStatus.Running + ? main.health + : healthChecks, + ), + startWith(healthChecks), + ) + + return isEmptyObject(healthChecks) ? null : healthChecks$ + } +} diff --git a/ui/src/app/pages/apps-routes/app-show/pipes/to-status.pipe.ts b/ui/src/app/pages/apps-routes/app-show/pipes/to-status.pipe.ts new file mode 100644 index 000000000..1fc9e83f3 --- /dev/null +++ b/ui/src/app/pages/apps-routes/app-show/pipes/to-status.pipe.ts @@ -0,0 +1,15 @@ +import { Pipe, PipeTransform } from '@angular/core' +import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { + PackageStatus, + renderPkgStatus, +} from 'src/app/services/pkg-status-rendering.service' + +@Pipe({ + name: 'toStatus', +}) +export class ToStatusPipe implements PipeTransform { + transform(pkg: PackageDataEntry): PackageStatus { + return renderPkgStatus(pkg) + } +} diff --git a/ui/src/app/pipes/install-state.pipe.ts b/ui/src/app/pipes/install-state.pipe.ts index 0bdffcaaf..2734da17e 100644 --- a/ui/src/app/pipes/install-state.pipe.ts +++ b/ui/src/app/pipes/install-state.pipe.ts @@ -1,12 +1,15 @@ import { Pipe, PipeTransform } from '@angular/core' import { InstallProgress } from '../services/patch-db/data-model' -import { packageLoadingProgress, ProgressData } from '../util/package-loading-progress' +import { + packageLoadingProgress, + ProgressData, +} from '../util/package-loading-progress' @Pipe({ name: 'installState', }) export class InstallState implements PipeTransform { - transform (loadData: InstallProgress): ProgressData { + transform(loadData: InstallProgress): ProgressData | null { return packageLoadingProgress(loadData) } } diff --git a/ui/src/app/services/api/api.service.factory.ts b/ui/src/app/services/api/api.service.factory.ts deleted file mode 100644 index 05d678195..000000000 --- a/ui/src/app/services/api/api.service.factory.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpService } from '../http.service' -import { MockApiService } from './embassy-mock-api.service' -import { LiveApiService } from './embassy-live-api.service' -import { ConfigService } from '../config.service' -import { LocalStorageBootstrap } from '../patch-db/local-storage-bootstrap' - -export function ApiServiceFactory (config: ConfigService, http: HttpService, bootstrapper: LocalStorageBootstrap) { - if (config.mocks.enabled) { - return new MockApiService(bootstrapper) - } else { - return new LiveApiService(http) - } -} diff --git a/ui/src/app/services/modal.service.ts b/ui/src/app/services/modal.service.ts new file mode 100644 index 000000000..77cbda2b4 --- /dev/null +++ b/ui/src/app/services/modal.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core' +import { ActivatedRoute } from '@angular/router' +import { ModalController } from '@ionic/angular' +import { DependentInfo } from 'src/app/util/misc.util' +import { AppConfigPage } from 'src/app/modals/app-config/app-config.page' + +@Injectable({ + providedIn: 'root', +}) +export class ModalService { + constructor( + private readonly route: ActivatedRoute, + private readonly modalCtrl: ModalController, + ) {} + + async presentModalConfig(componentProps: ComponentProps): Promise { + const modal = await this.modalCtrl.create({ + component: AppConfigPage, + componentProps, + }) + await modal.present() + } +} + +interface ComponentProps { + pkgId: string + dependentInfo?: DependentInfo +} diff --git a/ui/src/app/services/patch-db/data-model.ts b/ui/src/app/services/patch-db/data-model.ts index a1fd0516d..02a454f6d 100644 --- a/ui/src/app/services/patch-db/data-model.ts +++ b/ui/src/app/services/patch-db/data-model.ts @@ -122,14 +122,14 @@ export interface Manifest { stop: string | null } main: ActionImpl - 'health-checks': { [id: string]: ActionImpl & { name: string, description: string } } + 'health-checks': Record config: ConfigActions | null - volumes: { [id: string]: Volume } + volumes: Record 'min-os-version': string - interfaces: { [id: string]: InterfaceDef } + interfaces: Record backup: BackupActions migrations: Migrations - actions: { [id: string]: Action } + actions: Record permissions: any // @TODO 0.3.1 dependencies: DependencyInfo } diff --git a/ui/src/app/services/pkg-status-rendering.service.ts b/ui/src/app/services/pkg-status-rendering.service.ts index 50b333695..31094caf0 100644 --- a/ui/src/app/services/pkg-status-rendering.service.ts +++ b/ui/src/app/services/pkg-status-rendering.service.ts @@ -1,11 +1,18 @@ import { isEmptyObject } from '../util/misc.util' -import { PackageDataEntry, PackageMainStatus, PackageState, Status } from './patch-db/data-model' +import { + PackageDataEntry, + PackageMainStatus, + PackageState, + Status, +} from './patch-db/data-model' -export function renderPkgStatus (pkg: PackageDataEntry): { - primary: PrimaryStatus, - dependency: DependencyStatus | null, +export interface PackageStatus { + primary: PrimaryStatus + dependency: DependencyStatus | null health: HealthStatus | null -} { +} + +export function renderPkgStatus(pkg: PackageDataEntry): PackageStatus { let primary: PrimaryStatus let dependency: DependencyStatus | null = null let health: HealthStatus | null = null @@ -21,7 +28,7 @@ export function renderPkgStatus (pkg: PackageDataEntry): { return { primary, dependency, health } } -function getPrimaryStatus (status: Status): PrimaryStatus { +function getPrimaryStatus(status: Status): PrimaryStatus { if (!status.configured) { return PrimaryStatus.NeedsConfig } else { @@ -29,7 +36,7 @@ function getPrimaryStatus (status: Status): PrimaryStatus { } } -function getDependencyStatus (pkg: PackageDataEntry): DependencyStatus { +function getDependencyStatus(pkg: PackageDataEntry): DependencyStatus { const installed = pkg.installed if (isEmptyObject(installed['current-dependencies'])) return null @@ -39,7 +46,7 @@ function getDependencyStatus (pkg: PackageDataEntry): DependencyStatus { return depIds.length ? DependencyStatus.Warning : DependencyStatus.Satisfied } -function getHealthStatus (status: Status): HealthStatus { +function getHealthStatus(status: Status): HealthStatus { if (status.main.status === PackageMainStatus.Running) { const values = Object.values(status.main.health) if (values.some(h => h.result === 'failure')) { diff --git a/ui/src/app/services/ui-launcher.service.ts b/ui/src/app/services/ui-launcher.service.ts new file mode 100644 index 000000000..77a782476 --- /dev/null +++ b/ui/src/app/services/ui-launcher.service.ts @@ -0,0 +1,22 @@ +import { Inject, Injectable } from '@angular/core' +import { DOCUMENT } from '@angular/common' +import { PackageDataEntry } from './patch-db/data-model' +import { ConfigService } from './config.service' + +@Injectable({ + providedIn: 'root', +}) +export class UiLauncherService { + constructor( + @Inject(DOCUMENT) private readonly document: Document, + private readonly config: ConfigService, + ) {} + + launch(pkg: PackageDataEntry): void { + this.document.defaultView.open( + this.config.launchableURL(pkg), + '_blank', + 'noreferrer', + ) + } +} diff --git a/ui/src/app/util/get-package-info.ts b/ui/src/app/util/get-package-info.ts index 4dbe06540..bc9bf9c0d 100644 --- a/ui/src/app/util/get-package-info.ts +++ b/ui/src/app/util/get-package-info.ts @@ -13,15 +13,13 @@ import { } from './package-loading-progress' import { Subscription } from 'rxjs' -export function getPackageInfo (entry: PackageDataEntry): PkgInfo { +export function getPackageInfo(entry: PackageDataEntry): PkgInfo { const statuses = renderPkgStatus(entry) return { entry, primaryRendering: PrimaryRendering[statuses.primary], - installProgress: !isEmptyObject(entry['install-progress']) - ? packageLoadingProgress(entry['install-progress']) - : undefined, + installProgress: packageLoadingProgress(entry['install-progress']), error: statuses.health === HealthStatus.Failure || statuses.dependency === DependencyStatus.Warning, @@ -31,7 +29,7 @@ export function getPackageInfo (entry: PackageDataEntry): PkgInfo { export interface PkgInfo { entry: PackageDataEntry primaryRendering: StatusRendering - installProgress: ProgressData + installProgress: ProgressData | null error: boolean sub?: Subscription | null } diff --git a/ui/src/app/util/package-loading-progress.ts b/ui/src/app/util/package-loading-progress.ts index 58d97b553..229bfed1f 100644 --- a/ui/src/app/util/package-loading-progress.ts +++ b/ui/src/app/util/package-loading-progress.ts @@ -1,8 +1,13 @@ import { InstallProgress } from 'src/app/services/patch-db/data-model' +import { isEmptyObject } from './misc.util' -export function packageLoadingProgress ( +export function packageLoadingProgress( loadData: InstallProgress, -): ProgressData { +): ProgressData | null { + if (isEmptyObject(loadData)) { + return null + } + let { downloaded, validated, @@ -28,11 +33,10 @@ export function packageLoadingProgress ( unpackWeight * unpacked, ) - const denominator = Math.floor( size * (downloadWeight + validateWeight + unpackWeight), ) - const totalProgress = Math.floor(100 * numerator / denominator) + const totalProgress = Math.floor((100 * numerator) / denominator) return { totalProgress, diff --git a/ui/tslint.json b/ui/tslint.json index 6f53dafb6..a7dfe1cc0 100644 --- a/ui/tslint.json +++ b/ui/tslint.json @@ -2,7 +2,6 @@ "rules": { "no-unused-variable": true, "no-unused-expression": true, - "space-before-function-paren": true, "semicolon": [ true, "never" @@ -24,8 +23,7 @@ "check-type", "check-typecast", "check-type-operator", - "check-preblock", - "check-postbrace" + "check-preblock" ], "trailing-comma": [ true,