diff --git a/CHANGELOG.md b/CHANGELOG.md index 920b0694a7..9a119c3b7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,39 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [10.0.1-22](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-21...v10.0.1-22) (2026-03-23) + + +### Features + +* display portfolio submission time ([d717b27](https://github.com/b0ink/doubtfire-deploy/commit/d717b270eb80c0b0245e6b39f0b3f0567c379512)) +* require discussion before marking complete ([#1103](https://github.com/b0ink/doubtfire-deploy/issues/1103)) ([86ae886](https://github.com/b0ink/doubtfire-deploy/commit/86ae8865268ee19e4b3430ff679358cc075e1346)) + + +### Bug Fixes + +* avoid rendering the staff list twice ([9e5be59](https://github.com/b0ink/doubtfire-deploy/commit/9e5be591b9a0cf72d01efa5e28e5b2b9e7eba043)) +* only render if submission date is valid ([04986a0](https://github.com/b0ink/doubtfire-deploy/commit/04986a0e501f0185dbd199b8d2554133217f7e75)) + +### [10.0.1-21](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-20...v10.0.1-21) (2026-03-13) + +### [10.0.1-20](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-19...v10.0.1-20) (2026-03-13) + +### [10.0.1-19](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-18...v10.0.1-19) (2026-03-13) + + +### Features + +* view submission history + zip file editor ([#1104](https://github.com/b0ink/doubtfire-deploy/issues/1104)) ([d706f09](https://github.com/b0ink/doubtfire-deploy/commit/d706f0918285a2c6d69c6240a93b41e88506f771)) + +### [10.0.1-18](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-17...v10.0.1-18) (2026-03-11) + + +### Bug Fixes + +* convert emojis in submission comment to string with colons ([#1105](https://github.com/b0ink/doubtfire-deploy/issues/1105)) ([7c508cb](https://github.com/b0ink/doubtfire-deploy/commit/7c508cba8695fcd7b6df4aaa53a70066ecb137f3)) +* dont subtract tutor note count for reading your own notes ([8fe6171](https://github.com/b0ink/doubtfire-deploy/commit/8fe61718c0e263fa965b861763337af27c175b36)) + ### [10.0.1-17](https://github.com/b0ink/doubtfire-deploy/compare/v10.0.1-16...v10.0.1-17) (2026-03-06) diff --git a/package-lock.json b/package-lock.json index a95ab51ec5..19291ceb8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "doubtfire", - "version": "10.0.1-17", + "version": "10.0.1-22", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "doubtfire", - "version": "10.0.1-17", + "version": "10.0.1-22", "license": "AGPL-3.0", "dependencies": { "@angular/animations": "^17.3.6", @@ -23,7 +23,7 @@ "@angular/router": "^17.3.6", "@angular/service-worker": "^17.3.6", "@angular/upgrade": "^17.3.6", - "@ctrl/ngx-emoji-mart": "^9.2.0", + "@ctrl/ngx-emoji-mart": "^9.3.0", "@ngneat/hotkeys": "^4.0.0", "@ngstack/code-editor": "7.3.0", "@uirouter/angular": "^13.0", @@ -37,7 +37,7 @@ "angular-filter": "0.5.17", "angular-markdown-filter": "1.3.2", "angular-md5": "0.1.10", - "angular-mocks": "1.5.11", + "angular-mocks": "1.8.3", "angular-nvd3": "1.0.9", "angular-resource": "1.5.11", "angular-sanitize": "1.5.11", @@ -59,8 +59,9 @@ "html2canvas": "^1.4.1", "html5-qrcode": "^2.3.8", "jquery": "2.1.4", + "jszip": "^3.10.1", "lodash": "~4.17", - "lottie-web": "^5.12.2", + "lottie-web": "^5.13.0", "marked": "^11.1.0", "moment": "^2.29.4", "monaco-editor": "^0.44.0", @@ -74,7 +75,7 @@ "ngx-monaco-editor-v2": "^17.0.1", "nvd3": "1.8.6", "qrcode": "^1.5.4", - "rxjs": "~7.4.0", + "rxjs": "~7.8.2", "ts-md5": "^1.3.1", "tslib": "^2.6.2", "underscore.string": "2.3.3", @@ -89,13 +90,13 @@ "@angular-eslint/template-parser": "^17.3.0", "@angular/compiler-cli": "^17.3.6", "@angular/language-service": "^17.3.6", - "@commitlint/cli": "^16.0.1", - "@commitlint/config-conventional": "^17", + "@commitlint/cli": "^20.5.0", + "@commitlint/config-conventional": "^20", "@types/angular": "1.5.11", "@types/canvas-confetti": "^1.6.0", "@types/d3": "^3.5.17", "@types/file-saver": "^2.0.1", - "@types/jasmine": "~3.9.1", + "@types/jasmine": "~6.0.0", "@types/jasminewd2": "~2.0.3", "@types/lodash": "^4.14.115", "@types/node": "^20.9.0", @@ -2647,477 +2648,299 @@ } }, "node_modules/@commitlint/cli": { - "version": "16.3.0", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.5.0.tgz", + "integrity": "sha512-yNkyN/tuKTJS3wdVfsZ2tXDM4G4Gi7z+jW54Cki8N8tZqwKBltbIvUUrSbT4hz1bhW/h0CdR+5sCSpXD+wMKaQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/format": "^16.2.1", - "@commitlint/lint": "^16.2.4", - "@commitlint/load": "^16.3.0", - "@commitlint/read": "^16.2.1", - "@commitlint/types": "^16.2.1", - "lodash": "^4.17.19", - "resolve-from": "5.0.0", - "resolve-global": "1.0.0", + "@commitlint/format": "^20.5.0", + "@commitlint/lint": "^20.5.0", + "@commitlint/load": "^20.5.0", + "@commitlint/read": "^20.5.0", + "@commitlint/types": "^20.5.0", + "tinyexec": "^1.0.0", "yargs": "^17.0.0" }, "bin": { "commitlint": "cli.js" }, "engines": { - "node": ">=v12" + "node": ">=v18" } }, "node_modules/@commitlint/config-conventional": { - "version": "17.8.1", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.5.0.tgz", + "integrity": "sha512-t3Ni88rFw1XMa4nZHgOKJ8fIAT9M2j5TnKyTqJzsxea7FUetlNdYFus9dz+MhIRZmc16P0PPyEfh6X2d/qw8SA==", "dev": true, "license": "MIT", "dependencies": { - "conventional-changelog-conventionalcommits": "^6.1.0" + "@commitlint/types": "^20.5.0", + "conventional-changelog-conventionalcommits": "^9.2.0" }, "engines": { - "node": ">=v14" + "node": ">=v18" } }, "node_modules/@commitlint/config-validator": { - "version": "16.2.1", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-20.5.0.tgz", + "integrity": "sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^16.2.1", - "ajv": "^6.12.6" + "@commitlint/types": "^20.5.0", + "ajv": "^8.11.0" }, "engines": { - "node": ">=v12" + "node": ">=v18" } }, - "node_modules/@commitlint/config-validator/node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, "node_modules/@commitlint/ensure": { - "version": "16.2.1", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-20.5.0.tgz", + "integrity": "sha512-IpHqAUesBeW1EDDdjzJeaOxU9tnogLAyXLRBn03SHlj1SGENn2JGZqSWGkFvBJkJzfXAuCNtsoYzax+ZPS+puw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^16.2.1", - "lodash": "^4.17.19" + "@commitlint/types": "^20.5.0", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" }, "engines": { - "node": ">=v12" + "node": ">=v18" } }, "node_modules/@commitlint/execute-rule": { - "version": "16.2.1", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-20.0.0.tgz", + "integrity": "sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==", "dev": true, "license": "MIT", "engines": { - "node": ">=v12" + "node": ">=v18" } }, "node_modules/@commitlint/format": { - "version": "16.2.1", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-20.5.0.tgz", + "integrity": "sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^16.2.1", - "chalk": "^4.0.0" + "@commitlint/types": "^20.5.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=v12" - } - }, - "node_modules/@commitlint/format/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@commitlint/format/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "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/@commitlint/format/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@commitlint/format/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@commitlint/format/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@commitlint/format/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "node": ">=v18" } }, "node_modules/@commitlint/is-ignored": { - "version": "16.2.4", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-20.5.0.tgz", + "integrity": "sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^16.2.1", - "semver": "7.3.7" - }, - "engines": { - "node": ">=v12" - } - }, - "node_modules/@commitlint/is-ignored/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@commitlint/is-ignored/node_modules/semver": { - "version": "7.3.7", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@commitlint/types": "^20.5.0", + "semver": "^7.6.0" }, "engines": { - "node": ">=10" + "node": ">=v18" } }, - "node_modules/@commitlint/is-ignored/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, "node_modules/@commitlint/lint": { - "version": "16.2.4", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-20.5.0.tgz", + "integrity": "sha512-jiM3hNUdu04jFBf1VgPdjtIPvbuVfDTBAc6L98AWcoLjF5sYqkulBHBzlVWll4rMF1T5zeQFB6r//a+s+BBKlA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/is-ignored": "^16.2.4", - "@commitlint/parse": "^16.2.1", - "@commitlint/rules": "^16.2.4", - "@commitlint/types": "^16.2.1" + "@commitlint/is-ignored": "^20.5.0", + "@commitlint/parse": "^20.5.0", + "@commitlint/rules": "^20.5.0", + "@commitlint/types": "^20.5.0" }, "engines": { - "node": ">=v12" + "node": ">=v18" } }, "node_modules/@commitlint/load": { - "version": "16.3.0", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-20.5.0.tgz", + "integrity": "sha512-sLhhYTL/KxeOTZjjabKDhwidGZan84XKK1+XFkwDYL/4883kIajcz/dZFAhBJmZPtL8+nBx6bnkzA95YxPeDPw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^16.2.1", - "@commitlint/execute-rule": "^16.2.1", - "@commitlint/resolve-extends": "^16.2.1", - "@commitlint/types": "^16.2.1", - "@types/node": ">=12", - "chalk": "^4.0.0", - "cosmiconfig": "^7.0.0", - "cosmiconfig-typescript-loader": "^2.0.0", - "lodash": "^4.17.19", - "resolve-from": "^5.0.0", - "typescript": "^4.4.3" + "@commitlint/config-validator": "^20.5.0", + "@commitlint/execute-rule": "^20.0.0", + "@commitlint/resolve-extends": "^20.5.0", + "@commitlint/types": "^20.5.0", + "cosmiconfig": "^9.0.1", + "cosmiconfig-typescript-loader": "^6.1.0", + "is-plain-obj": "^4.1.0", + "lodash.mergewith": "^4.6.2", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=v12" - } - }, - "node_modules/@commitlint/load/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=v18" } }, - "node_modules/@commitlint/load/node_modules/chalk": { - "version": "4.1.2", + "node_modules/@commitlint/load/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@commitlint/load/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@commitlint/load/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@commitlint/load/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@commitlint/load/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@commitlint/load/node_modules/typescript": { - "version": "4.9.5", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@commitlint/message": { - "version": "16.2.1", + "version": "20.4.3", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-20.4.3.tgz", + "integrity": "sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=v12" + "node": ">=v18" } }, "node_modules/@commitlint/parse": { - "version": "16.2.1", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-20.5.0.tgz", + "integrity": "sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^16.2.1", - "conventional-changelog-angular": "^5.0.11", - "conventional-commits-parser": "^3.2.2" + "@commitlint/types": "^20.5.0", + "conventional-changelog-angular": "^8.2.0", + "conventional-commits-parser": "^6.3.0" }, "engines": { - "node": ">=v12" + "node": ">=v18" } }, "node_modules/@commitlint/read": { - "version": "16.2.1", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-20.5.0.tgz", + "integrity": "sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/top-level": "^16.2.1", - "@commitlint/types": "^16.2.1", - "fs-extra": "^10.0.0", - "git-raw-commits": "^2.0.0" + "@commitlint/top-level": "^20.4.3", + "@commitlint/types": "^20.5.0", + "git-raw-commits": "^5.0.0", + "minimist": "^1.2.8", + "tinyexec": "^1.0.0" }, "engines": { - "node": ">=v12" + "node": ">=v18" } }, "node_modules/@commitlint/resolve-extends": { - "version": "16.2.1", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-20.5.0.tgz", + "integrity": "sha512-3SHPWUW2v0tyspCTcfSsYml0gses92l6TlogwzvM2cbxDgmhSRc+fldDjvGkCXJrjSM87BBaWYTPWwwyASZRrg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^16.2.1", - "@commitlint/types": "^16.2.1", - "import-fresh": "^3.0.0", - "lodash": "^4.17.19", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" + "@commitlint/config-validator": "^20.5.0", + "@commitlint/types": "^20.5.0", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=v12" + "node": ">=v18" } }, "node_modules/@commitlint/rules": { - "version": "16.2.4", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-20.5.0.tgz", + "integrity": "sha512-5NdQXQEdnDPT5pK8O39ZA7HohzPRHEsDGU23cyVCNPQy4WegAbAwrQk3nIu7p2sl3dutPk8RZd91yKTrMTnRkQ==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/ensure": "^16.2.1", - "@commitlint/message": "^16.2.1", - "@commitlint/to-lines": "^16.2.1", - "@commitlint/types": "^16.2.1", - "execa": "^5.0.0" + "@commitlint/ensure": "^20.5.0", + "@commitlint/message": "^20.4.3", + "@commitlint/to-lines": "^20.0.0", + "@commitlint/types": "^20.5.0" }, "engines": { - "node": ">=v12" + "node": ">=v18" } }, "node_modules/@commitlint/to-lines": { - "version": "16.2.1", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-20.0.0.tgz", + "integrity": "sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==", "dev": true, "license": "MIT", "engines": { - "node": ">=v12" + "node": ">=v18" } }, "node_modules/@commitlint/top-level": { - "version": "16.2.1", + "version": "20.4.3", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-20.4.3.tgz", + "integrity": "sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==", "dev": true, "license": "MIT", "dependencies": { - "find-up": "^5.0.0" + "escalade": "^3.2.0" }, "engines": { - "node": ">=v12" + "node": ">=v18" } }, "node_modules/@commitlint/types": { - "version": "16.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": ">=v12" - } - }, - "node_modules/@commitlint/types/node_modules/ansi-styles": { - "version": "4.3.0", + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.5.0.tgz", + "integrity": "sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "conventional-commits-parser": "^6.3.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=v18" } }, - "node_modules/@commitlint/types/node_modules/chalk": { - "version": "4.1.2", + "node_modules/@conventional-changelog/git-client": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.6.0.tgz", + "integrity": "sha512-T+uPDciKf0/ioNNDpMGc8FDsehJClZP0yR3Q5MN6wE/Y/1QZ7F+80OgznnTCOlMEG4AV0LvH2UJi3C/nBnaBUg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@simple-libs/child-process-utils": "^1.0.0", + "@simple-libs/stream-utils": "^1.2.0", + "semver": "^7.5.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@commitlint/types/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" + "node": ">=18" }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@commitlint/types/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@commitlint/types/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@commitlint/types/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" + "peerDependencies": { + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.3.0" }, - "engines": { - "node": ">=8" + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } } }, "node_modules/@cspotcode/source-map-support": { @@ -3141,7 +2964,9 @@ } }, "node_modules/@ctrl/ngx-emoji-mart": { - "version": "9.2.0", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@ctrl/ngx-emoji-mart/-/ngx-emoji-mart-9.3.0.tgz", + "integrity": "sha512-9uFzAvlFT21OLsTfhL3ZEO5mp51qvL1F4ErIZVBIsvAlji46u6p2KGgVA60oIheFBX4JoZI7HBDGOkGnm9dTUQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -3936,6 +3761,96 @@ "node": ">= 0.4" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@material/animation": { "version": "15.0.0-canary.7f224ddd4.0", "license": "MIT", @@ -5171,206 +5086,348 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", - "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", - "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", - "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", - "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", - "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", - "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz", - "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", - "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", - "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", - "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", - "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz", - "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", - "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", - "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", - "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", - "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -5461,7 +5518,36 @@ "@sigstore/protobuf-specs": "^0.3.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@simple-libs/child-process-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz", + "integrity": "sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^1.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@simple-libs/stream-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", + "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" } }, "node_modules/@sinclair/typebox": { @@ -5611,7 +5697,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -5656,7 +5744,9 @@ } }, "node_modules/@types/jasmine": { - "version": "3.9.1", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-6.0.0.tgz", + "integrity": "sha512-18lgGsLmEh3VJk9eZ5wAjTISxdqzl6YOwu8UdMpolajN57QOCNbl+AbHUd+Yu9ItrsFdB+c8LSZSGNg8nHaguw==", "dev": true, "license": "MIT" }, @@ -5698,11 +5788,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "20.12.11", "dev": true, @@ -5719,16 +5804,6 @@ "@types/node": "*" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "dev": true, - "license": "MIT" - }, "node_modules/@types/q": { "version": "0.0.32", "dev": true, @@ -6248,7 +6323,7 @@ }, "node_modules/abbrev": { "version": "1.1.1", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/accepts": { @@ -6505,7 +6580,9 @@ "version": "0.1.10" }, "node_modules/angular-mocks": { - "version": "1.5.11", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.8.3.tgz", + "integrity": "sha512-vqsT6zwu80cZ8RY7qRQBZuy6Fq5X7/N5hkV9LzNT0c8b546rw4ErGK6muW1u2JnDKYa7+jJuaGM702bWir4HGw==", "license": "MIT" }, "node_modules/angular-nvd3": { @@ -6645,6 +6722,28 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "dev": true, @@ -7034,12 +7133,14 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.6.8", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -7691,6 +7792,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "dev": true, @@ -7723,22 +7837,6 @@ "node": ">= 6" } }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/caniuse-db": { "version": "1.0.30001617", "dev": true, @@ -7768,6 +7866,22 @@ "dev": true, "license": "MIT" }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/canvas-confetti": { "version": "1.9.3", "license": "ISC", @@ -8154,6 +8268,16 @@ "version": "1.1.3", "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colorette": { "version": "2.0.20", "dev": true, @@ -8267,7 +8391,7 @@ }, "node_modules/concat-map": { "version": "0.0.1", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/concurrently": { @@ -8368,6 +8492,13 @@ "date-now": "^0.1.4" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, "node_modules/constantinople": { "version": "3.1.2", "dev": true, @@ -8423,45 +8554,46 @@ "dev": true }, "node_modules/conventional-changelog-angular": { - "version": "5.0.13", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.0.tgz", + "integrity": "sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==", "dev": true, "license": "ISC", "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" + "compare-func": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/conventional-changelog-conventionalcommits": { - "version": "6.1.0", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.3.0.tgz", + "integrity": "sha512-kYFx6gAyjSIMwNtASkI3ZE99U1fuVDJr0yTYgVy+I2QG46zNZfl2her+0+eoviG82c5WQvW1jMt1eOQTeJLodA==", "dev": true, "license": "ISC", "dependencies": { "compare-func": "^2.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/conventional-commits-parser": { - "version": "3.2.4", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.3.0.tgz", + "integrity": "sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==", "dev": true, "license": "MIT", "dependencies": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.0.4", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" + "@simple-libs/stream-utils": "^1.2.0", + "meow": "^13.0.0" }, "bin": { - "conventional-commits-parser": "cli.js" + "conventional-commits-parser": "dist/cli/index.js" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/convert-source-map": { @@ -8587,7 +8719,6 @@ }, "node_modules/core-util-is": { "version": "1.0.2", - "dev": true, "license": "MIT" }, "node_modules/cors": { @@ -8603,36 +8734,78 @@ } }, "node_modules/cosmiconfig": { - "version": "7.1.0", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/cosmiconfig-typescript-loader": { - "version": "2.0.2", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", + "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", "dev": true, "license": "MIT", "dependencies": { - "cosmiconfig": "^7", - "ts-node": "^10.8.1" + "jiti": "^2.6.1" }, "engines": { - "node": ">=12", - "npm": ">=6" + "node": ">=v18" }, "peerDependencies": { "@types/node": "*", - "cosmiconfig": ">=7", - "typescript": ">=3" + "cosmiconfig": ">=9", + "typescript": ">=5" + } + }, + "node_modules/cosmiconfig-typescript-loader/node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/create-require": { @@ -8825,14 +8998,6 @@ "version": "3.5.17", "license": "BSD-3-Clause" }, - "node_modules/dargs": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/dashdash": { "version": "1.14.1", "dev": true, @@ -8944,35 +9109,25 @@ "node": ">=0.10.0" } }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", + "node_modules/decode-uri-component": { + "version": "0.2.2", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=0.10" } }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "dev": true, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/deep-is": { @@ -9129,6 +9284,13 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, "node_modules/depd": { "version": "2.0.0", "dev": true, @@ -9162,6 +9324,16 @@ "node": ">=0.10.0" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node": { "version": "2.1.0", "dev": true, @@ -9335,6 +9507,20 @@ "node": ">=12" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "dev": true, @@ -9583,11 +9769,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -9605,8 +9790,9 @@ "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "dev": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -9616,13 +9802,16 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -9738,7 +9927,9 @@ } }, "node_modules/escalade": { - "version": "3.1.2", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -10831,12 +11022,16 @@ } }, "node_modules/flatted": { - "version": "3.3.1", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.6", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { @@ -10921,12 +11116,16 @@ } }, "node_modules/form-data": { - "version": "4.0.0", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -10977,19 +11176,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fs-extra": { - "version": "10.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/fs-minipass": { "version": "3.0.3", "license": "ISC", @@ -11007,7 +11193,7 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/fsevents": { @@ -11056,6 +11242,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gaze": { "version": "1.1.3", "dev": true, @@ -11083,14 +11291,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -11107,6 +11322,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stdin": { "version": "4.0.1", "dev": true, @@ -11166,26 +11394,25 @@ } }, "node_modules/git-raw-commits": { - "version": "2.0.11", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz", + "integrity": "sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==", "dev": true, "license": "MIT", "dependencies": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" + "@conventional-changelog/git-client": "^2.6.0", + "meow": "^13.0.0" }, "bin": { - "git-raw-commits": "cli.js" + "git-raw-commits": "src/cli.js" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/glob": { "version": "7.1.7", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -11220,7 +11447,7 @@ }, "node_modules/glob/node_modules/brace-expansion": { "version": "1.1.11", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -11229,7 +11456,7 @@ }, "node_modules/glob/node_modules/minimatch": { "version": "3.1.2", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -11238,21 +11465,31 @@ "node": "*" } }, - "node_modules/global-dirs": { - "version": "0.1.1", + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", "dev": true, "license": "MIT", "dependencies": { - "ini": "^1.3.4" + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/global-dirs/node_modules/ini": { - "version": "1.3.8", + "node_modules/global-directory/node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/global-modules": { "version": "1.0.0", @@ -11374,10 +11611,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12586,14 +12825,6 @@ "dev": true, "license": "MIT" }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/has": { "version": "1.0.4", "dev": true, @@ -12649,6 +12880,7 @@ }, "node_modules/has-proto": { "version": "1.0.3", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -12658,7 +12890,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -12681,6 +12915,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, "node_modules/has-value": { "version": "1.0.0", "dev": true, @@ -12775,33 +13016,6 @@ "node": "*" } }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, "node_modules/hpack.js": { "version": "2.1.6", "dev": true, @@ -13133,11 +13347,12 @@ }, "node_modules/immediate": { "version": "3.0.6", - "dev": true, "license": "MIT" }, "node_modules/immutable": { - "version": "4.3.5", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz", + "integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==", "dev": true, "license": "MIT" }, @@ -13164,6 +13379,17 @@ "node": ">=4" } }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "license": "MIT", @@ -13180,7 +13406,7 @@ }, "node_modules/inflight": { "version": "1.0.6", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -13232,13 +13458,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.1", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/internal-slot": { "version": "1.0.7", "dev": true, @@ -13708,17 +13927,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-text-path": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "text-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-typed-array": { "version": "1.1.13", "dev": true, @@ -14527,21 +14735,6 @@ ], "license": "MIT" }, - "node_modules/JSONStream": { - "version": "1.3.5", - "dev": true, - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/jsprim": { "version": "1.4.2", "dev": true, @@ -14568,7 +14761,8 @@ }, "node_modules/jszip": { "version": "3.10.1", - "dev": true, + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", @@ -14579,12 +14773,10 @@ }, "node_modules/jszip/node_modules/isarray": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/jszip/node_modules/readable-stream": { "version": "2.3.8", - "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -14598,7 +14790,6 @@ }, "node_modules/jszip/node_modules/string_decoder": { "version": "1.1.1", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -15009,7 +15200,6 @@ }, "node_modules/lie": { "version": "3.3.0", - "dev": true, "license": "MIT", "dependencies": { "immediate": "~3.0.5" @@ -15145,6 +15335,13 @@ "version": "4.17.21", "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "dev": true, @@ -15155,11 +15352,46 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "dev": true, "license": "MIT" }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true, + "license": "MIT" + }, "node_modules/log-symbols": { "version": "4.1.0", "license": "MIT", @@ -15257,7 +15489,9 @@ } }, "node_modules/lottie-web": { - "version": "5.12.2", + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.13.0.tgz", + "integrity": "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==", "license": "MIT" }, "node_modules/lower-case": { @@ -15349,17 +15583,6 @@ "node": ">=0.10.0" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/map-visit": { "version": "1.0.0", "dev": true, @@ -15381,6 +15604,15 @@ "node": ">= 18" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "dev": true, @@ -15408,48 +15640,18 @@ } }, "node_modules/meow": { - "version": "8.1.2", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/meow/node_modules/yargs-parser": { - "version": "20.2.9", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/merge-descriptors": { "version": "1.0.1", "dev": true, @@ -15536,12 +15738,17 @@ "node": ">=6" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "dev": true, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", "license": "MIT", + "optional": true, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mini-css-extract-plugin": { @@ -15589,35 +15796,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimist-options": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/minimist-options/node_modules/arrify": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/minimist-options/node_modules/is-plain-obj": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/minipass": { "version": "7.1.1", "license": "ISC", @@ -15924,7 +16102,6 @@ "version": "2.22.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", - "dev": true, "optional": true }, "node_modules/nanoid": { @@ -16197,8 +16374,31 @@ "license": "MIT", "optional": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-forge": { - "version": "1.3.1", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { @@ -16319,20 +16519,6 @@ "nopt": "bin/nopt.js" } }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "devOptional": true, @@ -16569,6 +16755,20 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "dev": true, @@ -16882,7 +17082,7 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17043,7 +17243,7 @@ }, "node_modules/once": { "version": "1.4.0", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -17335,7 +17535,6 @@ }, "node_modules/pako": { "version": "1.0.11", - "dev": true, "license": "(MIT AND Zlib)" }, "node_modules/param-case": { @@ -17478,7 +17677,7 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17578,7 +17777,9 @@ "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.0", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, @@ -17986,47 +18187,6 @@ } } }, - "node_modules/postcss-loader/node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/postcss-loader/node_modules/cosmiconfig": { - "version": "9.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/postcss-loader/node_modules/js-yaml": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/postcss-media-query-parser": { "version": "0.2.3", "dev": true, @@ -18302,7 +18462,6 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", - "dev": true, "license": "MIT" }, "node_modules/promise": { @@ -18957,14 +19116,6 @@ ], "license": "MIT" }, - "node_modules/quick-lru": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/randombytes": { "version": "2.1.0", "dev": true, @@ -19114,124 +19265,6 @@ "node": ">=4" } }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/hosted-git-info": { - "version": "2.8.9", - "dev": true, - "license": "ISC" - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/normalize-package-data": { - "version": "2.5.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/read-pkg": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, "node_modules/read-pkg/node_modules/hosted-git-info": { "version": "2.8.9", "dev": true, @@ -19312,18 +19345,6 @@ "node": ">= 0.10" } }, - "node_modules/redent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/reflect-metadata": { "version": "0.2.2", "dev": true, @@ -19587,17 +19608,6 @@ "node": ">=8" } }, - "node_modules/resolve-global": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "global-dirs": "^0.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/resolve-pkg": { "version": "2.0.0", "dev": true, @@ -19704,7 +19714,7 @@ }, "node_modules/rimraf": { "version": "3.0.2", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -19717,11 +19727,13 @@ } }, "node_modules/rollup": { - "version": "4.17.2", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -19731,51 +19743,34 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.17.2", - "@rollup/rollup-android-arm64": "4.17.2", - "@rollup/rollup-darwin-arm64": "4.17.2", - "@rollup/rollup-darwin-x64": "4.17.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", - "@rollup/rollup-linux-arm-musleabihf": "4.17.2", - "@rollup/rollup-linux-arm64-gnu": "4.17.2", - "@rollup/rollup-linux-arm64-musl": "4.17.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", - "@rollup/rollup-linux-riscv64-gnu": "4.17.2", - "@rollup/rollup-linux-s390x-gnu": "4.17.2", - "@rollup/rollup-linux-x64-gnu": "4.17.2", - "@rollup/rollup-linux-x64-musl": "4.17.2", - "@rollup/rollup-win32-arm64-msvc": "4.17.2", - "@rollup/rollup-win32-ia32-msvc": "4.17.2", - "@rollup/rollup-win32-x64-msvc": "4.17.2", + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", "fsevents": "~2.3.2" } }, - "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", - "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", - "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/run-async": { "version": "3.0.0", "license": "MIT", @@ -19810,16 +19805,14 @@ "dev": true }, "node_modules/rxjs": { - "version": "7.4.0", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", "dependencies": { - "tslib": "~2.1.0" + "tslib": "^2.1.0" } }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.1.0", - "license": "0BSD" - }, "node_modules/safe-array-concat": { "version": "1.1.2", "dev": true, @@ -19839,7 +19832,6 @@ }, "node_modules/safe-buffer": { "version": "5.1.2", - "dev": true, "license": "MIT" }, "node_modules/safe-json-parse": { @@ -20281,7 +20273,6 @@ }, "node_modules/setimmediate": { "version": "1.0.5", - "dev": true, "license": "MIT" }, "node_modules/setprototypeof": { @@ -20523,11 +20514,44 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/simple-fmt": { "version": "0.1.0", "dev": true, "license": "MIT" }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/simple-is": { "version": "0.2.0", "dev": true, @@ -20896,14 +20920,6 @@ "node": ">=0.10.0" } }, - "node_modules/split2": { - "version": "3.2.2", - "dev": true, - "license": "ISC", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "dev": true, @@ -21162,17 +21178,6 @@ "node": ">=6" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "dev": true, @@ -21557,14 +21562,6 @@ "node": "*" } }, - "node_modules/text-extensions": { - "version": "1.9.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -21603,14 +21600,6 @@ "dev": true, "license": "MIT" }, - "node_modules/through2": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "3" - } - }, "node_modules/thunky": { "version": "1.1.0", "dev": true, @@ -21637,6 +21626,16 @@ "ms": "^2.1.1" } }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tmp": { "version": "0.2.3", "dev": true, @@ -21773,6 +21772,13 @@ "node": ">=0.8" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "optional": true + }, "node_modules/tree-kill": { "version": "1.2.2", "dev": true, @@ -21781,14 +21787,6 @@ "tree-kill": "cli.js" } }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/tryor": { "version": "0.1.2", "dev": true, @@ -23038,6 +23036,13 @@ "node": ">=0.8.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause", + "optional": true + }, "node_modules/webpack": { "version": "5.90.3", "dev": true, @@ -23343,6 +23348,17 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "license": "ISC", @@ -23393,6 +23409,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wildcard": { "version": "2.0.1", "dev": true, @@ -23528,7 +23554,7 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/ws": { @@ -23588,14 +23614,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "1.10.2", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, "node_modules/yargs": { "version": "17.7.2", "license": "MIT", diff --git a/package.json b/package.json index 0ebd47d9a2..6a353dbcaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "doubtfire", - "version": "10.0.1-17", + "version": "10.0.1-22", "homepage": "http://github.com/doubtfire-lms/", "description": "Learning and feedback tool.", "license": "AGPL-3.0", @@ -42,7 +42,7 @@ "@angular/router": "^17.3.6", "@angular/service-worker": "^17.3.6", "@angular/upgrade": "^17.3.6", - "@ctrl/ngx-emoji-mart": "^9.2.0", + "@ctrl/ngx-emoji-mart": "^9.3.0", "@ngneat/hotkeys": "^4.0.0", "@ngstack/code-editor": "7.3.0", "@uirouter/angular": "^13.0", @@ -56,7 +56,7 @@ "angular-filter": "0.5.17", "angular-markdown-filter": "1.3.2", "angular-md5": "0.1.10", - "angular-mocks": "1.5.11", + "angular-mocks": "1.8.3", "angular-nvd3": "1.0.9", "angular-resource": "1.5.11", "angular-sanitize": "1.5.11", @@ -78,8 +78,9 @@ "html2canvas": "^1.4.1", "html5-qrcode": "^2.3.8", "jquery": "2.1.4", + "jszip": "^3.10.1", "lodash": "~4.17", - "lottie-web": "^5.12.2", + "lottie-web": "^5.13.0", "marked": "^11.1.0", "moment": "^2.29.4", "monaco-editor": "^0.44.0", @@ -93,7 +94,7 @@ "ngx-monaco-editor-v2": "^17.0.1", "nvd3": "1.8.6", "qrcode": "^1.5.4", - "rxjs": "~7.4.0", + "rxjs": "~7.8.2", "ts-md5": "^1.3.1", "tslib": "^2.6.2", "underscore.string": "2.3.3", @@ -108,13 +109,13 @@ "@angular-eslint/template-parser": "^17.3.0", "@angular/compiler-cli": "^17.3.6", "@angular/language-service": "^17.3.6", - "@commitlint/cli": "^16.0.1", - "@commitlint/config-conventional": "^17", + "@commitlint/cli": "^20.5.0", + "@commitlint/config-conventional": "^20", "@types/angular": "1.5.11", "@types/canvas-confetti": "^1.6.0", "@types/d3": "^3.5.17", "@types/file-saver": "^2.0.1", - "@types/jasmine": "~3.9.1", + "@types/jasmine": "~6.0.0", "@types/jasminewd2": "~2.0.3", "@types/lodash": "^4.14.115", "@types/node": "^20.9.0", diff --git a/src/app/api/models/overseer/overseer-assessment.ts b/src/app/api/models/overseer/overseer-assessment.ts index 005b6be09f..a41bcbb5d2 100644 --- a/src/app/api/models/overseer/overseer-assessment.ts +++ b/src/app/api/models/overseer/overseer-assessment.ts @@ -1,4 +1,6 @@ import {Entity, EntityCache, EntityMapping} from 'ngx-entity-service'; +import {AppInjector} from 'src/app/app-injector'; +import {DoubtfireConstants} from 'src/app/config/constants/doubtfire-constants'; import {Task} from '../doubtfire-model'; import {OverseerStepResult} from './overseer-step-result'; @@ -19,6 +21,7 @@ export class OverseerAssessment extends Entity { passedSteps: number; label: string; + hasSubmissionFiles?: boolean; public readonly stepResultsCache: EntityCache = new EntityCache(); @@ -50,4 +53,11 @@ export class OverseerAssessment extends Entity { public get reportReady() { return this.submissionStatus === 'passed' || this.submissionStatus === 'failed'; } + + public submissionFilesUrl(): string { + const constants = AppInjector.get(DoubtfireConstants); + const timestamp = + this.timestampString ?? Math.floor(this.timestamp.getTime() / 1000).toString(); + return `${constants.API_URL}/projects/${this.task.project.id}/task_def_id/${this.task.definition.id}/submissions/timestamps/${timestamp}/files`; + } } diff --git a/src/app/api/models/project.ts b/src/app/api/models/project.ts index 6eed890270..b799ed01d6 100644 --- a/src/app/api/models/project.ts +++ b/src/app/api/models/project.ts @@ -45,6 +45,7 @@ export class Project extends Entity { public portfolioStatus: number; public portfolioFiles: {kind: string; name: string; idx: number}[]; public escalationAttemptsRemaining: number; + public portfolioSubmissionDate: Date; public taskStats: { key: TaskStatusEnum; diff --git a/src/app/api/models/task-definition.ts b/src/app/api/models/task-definition.ts index f556de8631..c8f88d1e9e 100644 --- a/src/app/api/models/task-definition.ts +++ b/src/app/api/models/task-definition.ts @@ -57,6 +57,7 @@ export class TaskDefinition extends Entity { similarityLanguage: string = 'c'; hasJplagReport: boolean; assessInPortfolioOnly: boolean; + requiresDiscussion: boolean; useResourcesForJplagBaseCode: boolean; lockAssessmentsToTutorialStream: boolean; discussionPromptsCount: number; diff --git a/src/app/api/models/task.ts b/src/app/api/models/task.ts index 906e5759d8..d8a5d9b19d 100644 --- a/src/app/api/models/task.ts +++ b/src/app/api/models/task.ts @@ -113,6 +113,18 @@ export class Task extends Entity { return this.commentCache.currentValues; } + public get hasDiscussedInClassComment(): boolean { + return this.comments.some((comment) => comment.commentType === 'discussed_in_class'); + } + + public get requiresDiscussionForComplete(): boolean { + return !!this.definition?.requiresDiscussion; + } + + public get canMarkComplete(): boolean { + return !this.requiresDiscussionForComplete || this.hasDiscussedInClassComment; + } + public get tutor(): UnitRole { const enrolments = this.project.tutorialEnrolmentsCache.currentValues.filter( (t) => t.tutorialStream.name === this.definition.tutorialStream.name, @@ -224,6 +236,9 @@ export class Task extends Entity { return this.definition.localDueDate(); } + public get checklocalDueDate(): Date { + return this.localDueDate(); + } public localDueDateString(): string { const locale: string = AppInjector.get(LOCALE_ID); @@ -243,7 +258,20 @@ export class Task extends Entity { return Math.ceil(diffInDays / 7); } + public get taskPeriodProgress() { + const today = new Date(); + //use Math.abs to avoid sign + if (today <= this.startDate) return 0; + if (today >= this.checklocalDueDate) return 50; + + const startToNow = Math.abs(today.valueOf() - this.startDate.valueOf()); + const totalDuration = Math.abs(this.taskTotalDuration); + return Math.round((startToNow / totalDuration) * 50); + } + public get taskTotalDuration(): number { + return this.checklocalDueDate.valueOf() - this.startDate.valueOf(); + } /** * Set the task to be due in a specific week. * @@ -818,6 +846,11 @@ export class Task extends Entity { const oldStatus = this.status; const alerts: AlertService = AppInjector.get(AlertService); + if (status === 'complete' && !this.canMarkComplete) { + alerts.error('This task must be discussed in class before it can marked complete.', 6000); + return; + } + const updateFunc = () => { const taskService: TaskService = AppInjector.get(TaskService); const options: RequestOptions = { diff --git a/src/app/api/services/overseer-assessment.service.ts b/src/app/api/services/overseer-assessment.service.ts index df897e43be..82c41e3dbc 100644 --- a/src/app/api/services/overseer-assessment.service.ts +++ b/src/app/api/services/overseer-assessment.service.ts @@ -53,6 +53,7 @@ export class OverseerAssessmentService extends EntityService 'overseerStepId', 'totalSteps', 'passedSteps', + 'hasSubmissionFiles', ); } diff --git a/src/app/api/services/project.service.ts b/src/app/api/services/project.service.ts index f5573dafcb..fe7aaa30d0 100644 --- a/src/app/api/services/project.service.ts +++ b/src/app/api/services/project.service.ts @@ -208,6 +208,7 @@ export class ProjectService extends CachedEntityService { }, }, 'escalationAttemptsRemaining', + 'portfolioSubmissionDate', ); this.mapping.addJsonKey( diff --git a/src/app/api/services/task-definition.service.ts b/src/app/api/services/task-definition.service.ts index f43d104ada..d420f2c1fc 100644 --- a/src/app/api/services/task-definition.service.ts +++ b/src/app/api/services/task-definition.service.ts @@ -39,6 +39,7 @@ export class TaskDefinitionService extends CachedEntityService { 'similarityLanguage', 'hasJplagReport', 'assessInPortfolioOnly', + 'requiresDiscussion', { keys: 'targetDate', toEntityFn: MappingFunctions.mapDateToEndOfDay, diff --git a/src/app/common/archive-viewer/archive-viewer.component.html b/src/app/common/archive-viewer/archive-viewer.component.html new file mode 100644 index 0000000000..da1de5293f --- /dev/null +++ b/src/app/common/archive-viewer/archive-viewer.component.html @@ -0,0 +1,193 @@ +
+ @if (isLoading) { +
+ + Loading archive... +
+ } @else if (errorMessage) { +
+ error_outline + {{ errorMessage }} +
+ } @else if (!archiveFile) { +
+ folder_zip + Select an archive to preview. +
+ } @else if (!hasFiles) { +
+ folder_off + No files to display. +
+ } @else { + @if (!readOnly && saveEndpoint) { +
+ +
+ } + + @if (navigationMode === 'tree') { +
+ +
+ +
+
+ } @else { +
+
+ + @for (file of files; track trackByPath($index, file); let i = $index) { + + {{ file.tabLabel }} + + } + +
+
+ + } + } +
+ + + @if (selectedFile; as file) { +
+ @if (showPreview) { + @if (file.isLoading || !file.isLoaded) { +
+ + Loading {{ file.name }}... +
+ } @else if (isArchiveCodeOrTextFile(file)) { +
+ + +
+ } @else if (isArchiveImageFile(file)) { +
+ +
+ +
+
+ } @else if (isArchivePdfFile(file)) { +
+ + +
+ } @else { +
+ insert_drive_file +
+

This file type cannot be previewed.

+ +
+
+ } + } +
+ } +
+ + + @for (node of nodes; track trackTreeNode($index, node)) { + @if (node.isDirectory) { +
+ folder + {{ node.name }} +
+ + } @else { + + } + } +
+ + +
+ {{ path }} +
+
diff --git a/src/app/common/archive-viewer/archive-viewer.component.scss b/src/app/common/archive-viewer/archive-viewer.component.scss new file mode 100644 index 0000000000..62defd6df8 --- /dev/null +++ b/src/app/common/archive-viewer/archive-viewer.component.scss @@ -0,0 +1,34 @@ +:host { + display: block; + min-width: 0; + width: 100%; + height: 100%; + min-height: 0; +} + +.archive-editor-wrapper, +.archive-pdf-wrapper { + width: 100%; + height: 100%; + min-height: 350px; + flex: 1 1 auto; + overflow: hidden; +} + +:host ::ng-deep .archive-monaco { + width: 100% !important; + height: 100% !important; +} + +:host ::ng-deep .archive-monaco .monaco-editor, +:host ::ng-deep .archive-monaco .overflow-guard, +:host ::ng-deep .archive-monaco .monaco-scrollable-element { + width: 100% !important; + height: 100% !important; +} + +.small-icon { + width: 12px; + height: 12px; + font-size: 12px; +} diff --git a/src/app/common/archive-viewer/archive-viewer.component.ts b/src/app/common/archive-viewer/archive-viewer.component.ts new file mode 100644 index 0000000000..152f261b25 --- /dev/null +++ b/src/app/common/archive-viewer/archive-viewer.component.ts @@ -0,0 +1,464 @@ +import {HttpClient, HttpResponse} from '@angular/common/http'; +import { + Component, + EventEmitter, + Input, + OnChanges, + OnDestroy, + Output, + SimpleChanges, +} from '@angular/core'; +import JSZip from 'jszip'; +import {firstValueFrom} from 'rxjs'; +import {AlertService} from '../services/alert.service'; +import { + ArchiveFileEntry, + classifyArchiveFile, + createArchiveFileEntry, + createArchiveFilePlaceholder, + getMonacoLanguageForPath, + getOrderedUploadFileIndex, + isArchiveCodeOrTextFile, + isArchiveImageFile, + isArchivePathHidden, + isArchivePdfFile, +} from './archive-viewer.helpers'; + +type ArchiveViewerNavigationMode = 'tabs' | 'tree'; + +interface ArchiveFileTreeNode { + key: string; + name: string; + path: string; + isDirectory: boolean; + fileIndex: number | null; + children: ArchiveFileTreeNode[]; +} + +@Component({ + selector: 'f-archive-viewer', + templateUrl: './archive-viewer.component.html', + styleUrls: ['./archive-viewer.component.scss'], +}) +export class ArchiveViewerComponent implements OnChanges, OnDestroy { + @Input() archiveFile: File | Blob | null = null; + @Input() navigationMode: ArchiveViewerNavigationMode = 'tabs'; + @Input() readOnly = true; + @Input() uploadRequirementNames: string[] = []; + @Input() showPreview = true; + @Input() preloadSelectedFile = false; + @Input() saveEndpoint?: string; + @Input() saveMethod: 'POST' | 'PUT' = 'POST'; + @Input() saveFieldName = 'file'; + @Input() saveFileName = 'archive.zip'; + + @Output() filesLoaded = new EventEmitter(); + @Output() saveSuccess = new EventEmitter>(); + @Output() saveError = new EventEmitter(); + @Output() selectedFileChanged = new EventEmitter(); + + public files: ArchiveFileEntry[] = []; + public selectedTab = 0; + public isLoading = false; + public isSaving = false; + public errorMessage: string | null = null; + public fileTreeNodes: ArchiveFileTreeNode[] = []; + + public readonly isArchiveCodeOrTextFile = isArchiveCodeOrTextFile; + public readonly isArchiveImageFile = isArchiveImageFile; + public readonly isArchivePdfFile = isArchivePdfFile; + + public readonly editorOptions = { + theme: 'vs', + language: 'plaintext', + automaticLayout: true, + scrollBeyondLastLine: false, + renderMinimap: false, + scrollbar: { + alwaysConsumeMouseWheel: false, + }, + minimap: { + enabled: false, + }, + }; + + public editorOptionsForSelectedFile = { + ...this.editorOptions, + language: 'plaintext', + readOnly: true, + }; + + constructor( + private http: HttpClient, + private alerts: AlertService, + ) {} + + public ngOnChanges(changes: SimpleChanges): void { + if (changes['archiveFile']) { + void this.loadArchive(); + } + + if (changes['uploadRequirementNames']) { + this.applyUploadRequirementLabels(this.files); + this.rebuildFileTree(); + } + + if (changes['readOnly']) { + this.updateEditorOptions(); + } + } + + public ngOnDestroy(): void { + this.clearFiles(); + } + + public get selectedFile(): ArchiveFileEntry | null { + return this.files[this.selectedTab] ?? null; + } + + public get hasFiles(): boolean { + return this.files.length > 0; + } + + public get hasDirtyFiles(): boolean { + return this.files.some((entry) => entry.dirty); + } + + public get canSave(): boolean { + return !this.readOnly && !!this.saveEndpoint && this.hasDirtyFiles && !this.isSaving; + } + + public trackByPath(index: number, file: ArchiveFileEntry): string { + return `${index}-${file.path}`; + } + + public trackTreeNode(_index: number, node: ArchiveFileTreeNode): string { + return node.key; + } + + public selectTreeNode(node: ArchiveFileTreeNode): void { + if (node.fileIndex === null) { + return; + } + + this.selectTab(node.fileIndex); + } + + public treeNodeLabel(node: ArchiveFileTreeNode): string { + return node.fileIndex === null ? node.name : this.files[node.fileIndex]?.tabLabel || node.name; + } + + public selectTab(index: number): void { + this.selectedTab = index; + this.updateEditorOptions(); + void this.loadAndPrepareSelectedFile(); + } + + public selectedFileLanguage(): string { + const selectedFile = this.selectedFile; + if (!selectedFile) { + return 'plaintext'; + } + + return selectedFile.language ?? getMonacoLanguageForPath(selectedFile.path) ?? 'plaintext'; + } + + public onEditorChange(value: string): void { + const selectedFile = this.selectedFile; + if ( + !selectedFile || + !selectedFile.isLoaded || + this.readOnly || + !isArchiveCodeOrTextFile(selectedFile) + ) { + return; + } + + selectedFile.textContent = value; + selectedFile.dirty = selectedFile.textContent !== selectedFile.originalTextContent; + } + + public async downloadSelectedFile(): Promise { + const file = this.selectedFile; + await this.ensureFileLoaded(file); + if (!file?.blobUrl) { + return; + } + + const downloadLink = document.createElement('a'); + downloadLink.href = file.blobUrl; + downloadLink.download = file.name; + document.body.appendChild(downloadLink); + downloadLink.click(); + downloadLink.parentNode?.removeChild(downloadLink); + } + + public async saveArchive(): Promise { + if (!this.canSave || !this.saveEndpoint) { + return; + } + + this.isSaving = true; + + try { + const zip = new JSZip(); + + for (const file of this.files) { + if (file.isLoaded && isArchiveCodeOrTextFile(file) && file.textContent !== undefined) { + const encoded = new TextEncoder().encode(file.textContent); + file.data = encoded; + file.originalTextContent = file.textContent; + zip.file(file.path, encoded); + continue; + } + + if (file.data) { + zip.file(file.path, file.data); + continue; + } + + const rawData = await file.zipObject.async('uint8array'); + zip.file(file.path, rawData); + } + + const archiveBlob = await zip.generateAsync({type: 'blob'}); + const archiveUpload = new File([archiveBlob], this.saveFileName, {type: 'application/zip'}); + const formData = new FormData(); + formData.append(this.saveFieldName, archiveUpload); + + const request = + this.saveMethod === 'PUT' + ? this.http.put(this.saveEndpoint, formData, {observe: 'response'}) + : this.http.post(this.saveEndpoint, formData, {observe: 'response'}); + + const response = await firstValueFrom(request); + for (const file of this.files) { + file.dirty = false; + } + + this.alerts.success('Archive saved', 3000); + this.saveSuccess.emit(response); + } catch (error) { + this.alerts.error(`Failed to save archive: ${error}`, 6000); + this.saveError.emit(error); + } finally { + this.isSaving = false; + } + } + + private async loadArchive(): Promise { + const requestedArchive = this.archiveFile; + + this.errorMessage = null; + this.selectedTab = 0; + this.clearFiles(); + + if (!requestedArchive) { + return; + } + + this.isLoading = true; + + try { + const zipData = await requestedArchive.arrayBuffer(); + const zip = await JSZip.loadAsync(zipData); + const paths = Object.keys(zip.files).sort((a, b) => a.localeCompare(b)); + const loadedFiles: ArchiveFileEntry[] = []; + + for (const path of paths) { + const zipFile = zip.files[path]; + if (!zipFile || zipFile.dir || isArchivePathHidden(path)) { + continue; + } + + loadedFiles.push(createArchiveFilePlaceholder(path, zipFile)); + } + + if (requestedArchive !== this.archiveFile) { + this.revokeUrls(loadedFiles); + return; + } + + this.files = loadedFiles; + this.applyUploadRequirementLabels(this.files); + this.rebuildFileTree(); + this.filesLoaded.emit(this.files.length); + + if (this.files.length === 0) { + this.errorMessage = 'This archive does not contain any files.'; + this.selectedFileChanged.emit(null); + return; + } + + this.updateEditorOptions(); + if (this.showPreview || this.preloadSelectedFile) { + void this.loadAndPrepareSelectedFile(); + } else { + this.selectedFileChanged.emit(null); + } + } catch { + this.errorMessage = 'Unable to read archive. Please provide a valid zip file.'; + this.filesLoaded.emit(0); + } finally { + if (requestedArchive === this.archiveFile) { + this.isLoading = false; + } + } + } + + private clearFiles(): void { + this.revokeUrls(this.files); + this.files = []; + this.fileTreeNodes = []; + this.selectedFileChanged.emit(null); + } + + private applyUploadRequirementLabels(files: ArchiveFileEntry[]): void { + for (let fileIndex = 0; fileIndex < files.length; fileIndex++) { + const file = files[fileIndex]; + const orderedIndex = getOrderedUploadFileIndex(file.path); + const requirementIndex = orderedIndex ?? fileIndex; + const requirementName = this.uploadRequirementNames[requirementIndex]; + file.tabLabel = requirementName?.trim() || file.name; + } + } + + private rebuildFileTree(): void { + const rootNodes: ArchiveFileTreeNode[] = []; + + const getOrCreateNode = ( + siblings: ArchiveFileTreeNode[], + node: Pick, + ): ArchiveFileTreeNode => { + const existing = siblings.find( + (candidate) => + candidate.name === node.name && + candidate.path === node.path && + candidate.isDirectory === node.isDirectory, + ); + if (existing) { + if (!node.isDirectory && node.fileIndex !== null) { + existing.fileIndex = node.fileIndex; + } + return existing; + } + + const createdNode: ArchiveFileTreeNode = { + key: `${node.isDirectory ? 'dir' : 'file'}:${node.path}`, + name: node.name, + path: node.path, + isDirectory: node.isDirectory, + fileIndex: node.fileIndex, + children: [], + }; + siblings.push(createdNode); + return createdNode; + }; + + for (let fileIndex = 0; fileIndex < this.files.length; fileIndex++) { + const file = this.files[fileIndex]; + const segments = file.path.split('/').filter((segment) => segment.length > 0); + if (segments.length === 0) { + continue; + } + + let currentNodes = rootNodes; + let currentPath = ''; + for (let i = 0; i < segments.length; i++) { + const segment = segments[i]; + const isFile = i === segments.length - 1; + currentPath = currentPath ? `${currentPath}/${segment}` : segment; + + const node = getOrCreateNode(currentNodes, { + name: segment, + path: currentPath, + isDirectory: !isFile, + fileIndex: isFile ? fileIndex : null, + }); + + if (!isFile) { + currentNodes = node.children; + } + } + } + + const sortNodes = (nodes: ArchiveFileTreeNode[]): void => { + nodes.sort((a, b) => { + if (a.isDirectory !== b.isDirectory) { + return a.isDirectory ? -1 : 1; + } + return a.name.localeCompare(b.name); + }); + + for (const node of nodes) { + if (node.children.length > 0) { + sortNodes(node.children); + } + } + }; + + sortNodes(rootNodes); + this.fileTreeNodes = rootNodes; + } + + private revokeUrls(files: ArchiveFileEntry[]): void { + for (const file of files) { + if (file.blobUrl) { + URL.revokeObjectURL(file.blobUrl); + } + } + } + + private async ensureFileLoaded(file: ArchiveFileEntry | null): Promise { + if (!file || file.isLoaded || file.isLoading) { + return; + } + + file.isLoading = true; + try { + const data = await file.zipObject.async('uint8array'); + const classification = classifyArchiveFile(file.path, data); + const enriched = createArchiveFileEntry(file, classification, data); + Object.assign(file, enriched); + if (file === this.selectedFile) { + this.updateEditorOptions(); + } + } catch { + file.isLoading = false; + this.alerts.error(`Unable to open file '${file.path}' from archive`, 6000); + } + } + + private async loadAndPrepareSelectedFile(): Promise { + const file = this.selectedFile; + await this.ensureFileLoaded(file); + + if (!file || file !== this.selectedFile || !file.isLoaded) { + this.selectedFileChanged.emit(null); + return; + } + + this.prepareFileForDisplay(file); + this.selectedFileChanged.emit(file); + } + + private prepareFileForDisplay(file: ArchiveFileEntry): void { + if (file.kind !== 'pdf' || !file.blob) { + return; + } + + if (file.blobUrl) { + URL.revokeObjectURL(file.blobUrl); + } + + file.blobUrl = URL.createObjectURL(file.blob); + } + + private updateEditorOptions(): void { + this.editorOptionsForSelectedFile = { + ...this.editorOptions, + language: this.selectedFileLanguage(), + readOnly: this.readOnly, + }; + } +} diff --git a/src/app/common/archive-viewer/archive-viewer.helpers.ts b/src/app/common/archive-viewer/archive-viewer.helpers.ts new file mode 100644 index 0000000000..f85d74976e --- /dev/null +++ b/src/app/common/archive-viewer/archive-viewer.helpers.ts @@ -0,0 +1,252 @@ +import * as monaco from 'monaco-editor'; +import JSZip from 'jszip'; + +export type ArchiveFileKind = 'code' | 'image' | 'pdf' | 'text' | 'binary'; + +export interface ArchiveFileClassification { + kind: ArchiveFileKind; + mimeType: string; + textContent?: string; + language?: string; +} + +export interface ArchiveFileEntry { + path: string; + name: string; + tabLabel: string; + kind: ArchiveFileKind; + mimeType: string; + language?: string; + textContent?: string; + originalTextContent?: string; + data?: Uint8Array; + blob?: Blob; + blobUrl?: string; + dirty: boolean; + isLoaded: boolean; + isLoading: boolean; + zipObject: JSZip.JSZipObject; +} + +type ArchiveFileKindLike = Pick | null | undefined; +type ArchiveFilePreviewLike = Pick | null | undefined; + +export function isArchiveCodeOrTextFile(file: ArchiveFileKindLike): boolean { + return file?.kind === 'code' || file?.kind === 'text'; +} + +export function isArchiveImageFile(file: ArchiveFilePreviewLike): boolean { + return file?.kind === 'image' && !!file.blobUrl; +} + +export function isArchivePdfFile(file: ArchiveFilePreviewLike): boolean { + return file?.kind === 'pdf' && !!file.blobUrl; +} + +export function createArchiveFilePlaceholder( + path: string, + zipObject: JSZip.JSZipObject, +): ArchiveFileEntry { + const name = getBaseName(path); + return { + path, + name, + tabLabel: name, + kind: 'binary', + mimeType: 'application/octet-stream', + dirty: false, + isLoaded: false, + isLoading: false, + zipObject, + }; +} + +export function getOrderedUploadFileIndex(path: string): number | null { + const fileName = getBaseName(path); + const match = fileName.match(/^(\d+)-/); + if (!match) { + return null; + } + + const parsed = Number.parseInt(match[1], 10); + return Number.isFinite(parsed) && parsed >= 0 ? parsed : null; +} + +export function isArchivePathHidden(path: string): boolean { + const segments = path.split('/').filter((segment) => segment.length > 0); + return segments.some((segment) => segment.startsWith('.') || segment === '__MACOSX'); +} + +export function createArchiveFileEntry( + existing: ArchiveFileEntry, + classification: ArchiveFileClassification, + data: Uint8Array, +): ArchiveFileEntry { + const blob = new Blob([data], {type: classification.mimeType}); + const shouldCreateBlobUrl = + classification.kind === 'image' || + classification.kind === 'pdf' || + classification.kind === 'binary'; + + return { + ...existing, + kind: classification.kind, + mimeType: classification.mimeType, + language: classification.language, + textContent: classification.textContent, + originalTextContent: classification.textContent, + data, + blob, + blobUrl: shouldCreateBlobUrl ? URL.createObjectURL(blob) : undefined, + dirty: false, + isLoaded: true, + isLoading: false, + }; +} + +export function classifyArchiveFile(path: string, data: Uint8Array): ArchiveFileClassification { + const detectedMimeType = detectMimeType(data); + if (detectedMimeType === 'application/pdf') { + return {kind: 'pdf', mimeType: detectedMimeType}; + } + + if (detectedMimeType.startsWith('image/')) { + return {kind: 'image', mimeType: detectedMimeType}; + } + + if (isProbablyText(data)) { + const decoded = decodeUtf8(data); + if (decoded !== null) { + const language = getMonacoLanguageForPath(path); + if (language && language !== 'plaintext') { + return {kind: 'code', mimeType: 'text/plain', textContent: decoded, language}; + } + + return {kind: 'text', mimeType: 'text/plain', textContent: decoded, language: 'plaintext'}; + } + } + + return {kind: 'binary', mimeType: detectedMimeType || 'application/octet-stream'}; +} + +export function getMonacoLanguageForPath(path: string): string | undefined { + const fileName = getBaseName(path).toLowerCase(); + const allLanguages = monaco?.languages?.getLanguages?.() ?? []; + + for (const language of allLanguages) { + if (language.filenames?.some((f) => f.toLowerCase() === fileName)) { + return language.id; + } + if (language.extensions?.some((ext) => fileName.endsWith(ext.toLowerCase()))) { + return language.id; + } + } + + return undefined; +} + +function getBaseName(path: string): string { + const lastSlash = path.lastIndexOf('/'); + return lastSlash >= 0 ? path.substring(lastSlash + 1) : path; +} + +function decodeUtf8(data: Uint8Array): string | null { + try { + return new TextDecoder('utf-8', {fatal: true}).decode(data); + } catch { + return null; + } +} + +function isProbablyText(data: Uint8Array): boolean { + if (data.length === 0) { + return true; + } + + const limit = Math.min(data.length, 8192); + let suspicious = 0; + + for (let i = 0; i < limit; i++) { + const byte = data[i]; + + if (byte === 0) { + return false; + } + + const isPrintableAscii = byte >= 32 && byte <= 126; + const isAllowedControl = byte === 9 || byte === 10 || byte === 13; + const isUtf8Extended = byte >= 128; + + if (!isPrintableAscii && !isAllowedControl && !isUtf8Extended) { + suspicious++; + } + } + + return suspicious / limit < 0.02; +} + +function detectMimeType(data: Uint8Array): string { + if (startsWithBytes(data, [0x25, 0x50, 0x44, 0x46, 0x2d])) { + return 'application/pdf'; + } + if (startsWithBytes(data, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) { + return 'image/png'; + } + if (startsWithBytes(data, [0xff, 0xd8, 0xff])) { + return 'image/jpeg'; + } + if (startsWithString(data, 'GIF87a') || startsWithString(data, 'GIF89a')) { + return 'image/gif'; + } + if (startsWithString(data, 'BM')) { + return 'image/bmp'; + } + if ( + startsWithString(data, 'RIFF') && + data.length >= 12 && + String.fromCharCode(...Array.from(data.slice(8, 12))) === 'WEBP' + ) { + return 'image/webp'; + } + if (isSvg(data)) { + return 'image/svg+xml'; + } + + return 'application/octet-stream'; +} + +function isSvg(data: Uint8Array): boolean { + const decoded = decodeUtf8(data.slice(0, 4096)); + if (!decoded) { + return false; + } + + const trimmed = decoded.trim().toLowerCase(); + return trimmed.includes(''); +} + +function startsWithBytes(data: Uint8Array, signature: number[]): boolean { + if (data.length < signature.length) { + return false; + } + + for (let i = 0; i < signature.length; i++) { + if (data[i] !== signature[i]) { + return false; + } + } + + return true; +} + +function startsWithString(data: Uint8Array, signature: string): boolean { + if (data.length < signature.length) { + return false; + } + for (let i = 0; i < signature.length; i++) { + if (data[i] !== signature.charCodeAt(i)) { + return false; + } + } + return true; +} diff --git a/src/app/common/footer/footer.component.html b/src/app/common/footer/footer.component.html index 22316673e0..cb6e238c38 100644 --- a/src/app/common/footer/footer.component.html +++ b/src/app/common/footer/footer.component.html @@ -11,123 +11,163 @@ } -
- -
- -
- - - -
+ + +
- @if (selectedTask && selectedTask.suggestedTaskStatus) { - - } - @if (selectedTask?.definition?.assessInPortfolioOnly) { - - } @else { - - } - +
+ - + @if (selectedTask && selectedTask.suggestedTaskStatus) { + + } -
+ + + @if (selectedTask?.definition?.assessInPortfolioOnly) { + + } @else { +
- + } + + + +
+ +
+ @if (viewType !== 'moderation' && !hideMainActionButtonsForModeration) { + + } @if (selectedTask && !selectedTask.loadingSubmissionDetails) { @if (viewType === 'moderation') { - - - +
+
+ @if (!hideMainActionButtonsForModeration) { + + + } @else { + + } + + + + + @if (hideMainActionButtonsForModeration) { + + } @else { + + } + +
+
} @if (viewType === 'overflow' || selectedTask.claimedByUnitRoleId) { @@ -249,6 +289,11 @@ View overseer + + diff --git a/src/app/common/modals/date-change-modal/task-date-slider.component.ts b/src/app/common/modals/date-change-modal/task-date-slider.component.ts index 4da6b3a2f8..90f281ec32 100644 --- a/src/app/common/modals/date-change-modal/task-date-slider.component.ts +++ b/src/app/common/modals/date-change-modal/task-date-slider.component.ts @@ -32,7 +32,7 @@ export class TaskDateSliderComponent implements OnChanges { ) {} public get max(): number { - return this.task.unit.totalWeeks + Math.ceil(this.task.project.specConDays / 7); + return 50; } ngOnChanges(changes: SimpleChanges): void { @@ -41,7 +41,7 @@ export class TaskDateSliderComponent implements OnChanges { this.cancelEdit(); } - this.value = this.task.dueWeek; + this.value = this.task.taskPeriodProgress; this._originalDueDate = this.task.dueDate; this._originalExtension = this.task.extensions; } diff --git a/src/app/common/pdf-viewer/pdf-viewer.component.ts b/src/app/common/pdf-viewer/pdf-viewer.component.ts index c019c17fc3..97c4ff5100 100644 --- a/src/app/common/pdf-viewer/pdf-viewer.component.ts +++ b/src/app/common/pdf-viewer/pdf-viewer.component.ts @@ -73,7 +73,11 @@ export class fPdfViewerComponent implements OnDestroy, OnChanges, AfterViewInit this._pdfUrl = value; this.loaded = false; this.pdfHasRendered = false; - this.downloadBlob(value); + if (value?.startsWith('blob:')) { + this.pdfBlobUrl = value; + } else { + this.downloadBlob(value); + } } } diff --git a/src/app/common/task-badge/task-badge.component.css b/src/app/common/task-badge/task-badge.component.css index f81d9056aa..8c791270f5 100644 --- a/src/app/common/task-badge/task-badge.component.css +++ b/src/app/common/task-badge/task-badge.component.css @@ -3,6 +3,15 @@ position: relative; } +.task-badge { + color: #111827; +} + +.task-badge.task-badge-highlight { + background-color: var(--mdc-theme-primary, #3939ff) !important; + color: #ffffff; +} + .mat-icon { font-size: 50px; height: 50px; diff --git a/src/app/common/task-badge/task-badge.component.html b/src/app/common/task-badge/task-badge.component.html index 565c943555..dfa63dd941 100644 --- a/src/app/common/task-badge/task-badge.component.html +++ b/src/app/common/task-badge/task-badge.component.html @@ -1,5 +1,8 @@ -
-

+

+

{{ abbreviation }}

diff --git a/src/app/common/task-badge/task-badge.component.ts b/src/app/common/task-badge/task-badge.component.ts index 01430984c5..d050a8fe4f 100644 --- a/src/app/common/task-badge/task-badge.component.ts +++ b/src/app/common/task-badge/task-badge.component.ts @@ -9,6 +9,7 @@ import {TaskDefinition} from 'src/app/api/models/task-definition'; export class FTaskBadgeComponent implements OnInit { @Input() taskDef: TaskDefinition; @Input() size = 100; + @Input() highlight = false; lineHeight = 12; diff --git a/src/app/doubtfire-angular.module.ts b/src/app/doubtfire-angular.module.ts index 43ae68e4b8..ae030bcc3e 100644 --- a/src/app/doubtfire-angular.module.ts +++ b/src/app/doubtfire-angular.module.ts @@ -218,6 +218,7 @@ import {TeachingPeriodListComponent} from './admin/states/teaching-periods/teach import {FChipComponent} from './common/f-chip/f-chip.component'; import {TaskSimilarityViewComponent} from './projects/states/dashboard/directives/task-dashboard/directives/task-similarity-view/task-similarity-view.component'; import {FileViewerComponent} from './common/file-viewer/file-viewer.component'; +import {ArchiveViewerComponent} from './common/archive-viewer/archive-viewer.component'; import {TaskDefinitionEditorComponent} from './units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-editor.component'; import {TaskDefinitionGeneralComponent} from './units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-general/task-definition-general.component'; import {TaskDefinitionWhoComponent} from './units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-who/task-definition-who.component'; @@ -312,6 +313,7 @@ import {TaskPlannerPrerequisitesModalService} from './projects/states/plan/task- import {TaskPlannerCardComponent} from './projects/states/dashboard/directives/progress-dashboard/task-planner-card/task-planner-card.component'; import {OverseerStepService} from './api/services/overseer-step.service'; import {TaskOverseerReportComponent} from './projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/task-overseer-report.component'; +import {SubmissionFilesModalComponent} from './projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/submission-files-modal/submission-files-modal.component'; import {OverseerStepResultService} from './api/services/overseer-step-result.service'; import {TutorNotesComponent} from './projects/states/tutor-notes/tutor-notes.component'; import {TutorNotesViewComponent} from './projects/states/dashboard/directives/task-dashboard/directives/tutor-notes-view/tutor-notes-view.component'; @@ -461,6 +463,7 @@ const GANTT_CHART_CONFIG = { UnitCodeComponent, NewTeachingPeriodDialogComponent, FileViewerComponent, + ArchiveViewerComponent, AlertComponent, FUnitTaskListComponent, FTaskDetailsViewComponent, @@ -510,6 +513,7 @@ const GANTT_CHART_CONFIG = { TaskPlannerCardComponent, TaskPlannerPrerequisitesModalComponent, TaskOverseerReportComponent, + SubmissionFilesModalComponent, TutorNotesComponent, TutorNotesViewComponent, ModerationComponent, @@ -675,6 +679,7 @@ const GANTT_CHART_CONFIG = { MatDialogModuleNew, CalendarModule.forRoot({provide: CalendarDateAdapter, useFactory: adapterFactory}), CodeEditorModule.forRoot(), + MatSidenavModule, NgxGanttModule, MatSidenavModule, MonacoEditorModule.forRoot(), diff --git a/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/submission-files-modal/submission-files-modal.component.html b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/submission-files-modal/submission-files-modal.component.html new file mode 100644 index 0000000000..5d1d69f412 --- /dev/null +++ b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/submission-files-modal/submission-files-modal.component.html @@ -0,0 +1,214 @@ +
+
+

+ @if (compareMode) { + Compare Submission Files + } @else { + Submission Files + } +

+
+ {{ data.assessment.task.definition.abbreviation }} + {{ data.assessment.task.definition.name }} ({{ data.assessment.task.project.student.name }}) +
+
+ + +
+ +
+ @if (isLoading) { +
+ +
+ } @else if (errorMessage) { +
+ {{ errorMessage }} +
+ } @else if (compareMode) { + @if (archiveBlob && comparedArchiveBlob) { +
+
+
+ + +
+ +
+ + @if (primaryArchiveParsed) { + + } @else { +
+ Loading archive... +
+ } +
+
+ +
+ @if (!bothSelectionsReady) { +
+ Select a file in both submissions to compare. +
+ } @else if (canShowDiffEditor) { +
+
+
+ {{ primarySelectedFile?.path ?? primarySelectedFile?.name }} +
+
+ {{ comparedSelectedFile?.path ?? comparedSelectedFile?.name }} +
+
+ +
+ } @else { +
+ + +
+ } +
+
+ } @else { +
+ Unable to load one or both submission archives. +
+ } + } @else if (archiveBlob) { +
+ + +
+ } @else { +
+ Unable to load submission files. +
+ } +
+ + +
+ Submission{{ number !== undefined ? ' ' + number : '' + }}{{ isMostRecent ? ' (Most recent)' : '' }}: + {{ timestamp | date: 'dd/MM/yyyy HH:mm' }} +
+
+ + +
+
+ + {{ file?.path ?? file?.name }} + + @if (selectedFilesMatch !== null) { + + {{ selectedFilesMatch ? 'Identical' : 'Different' }} + + } +
+ @if (isArchiveCodeOrTextFile(file)) { + + } @else if (isArchivePdfFile(file)) { + + } @else if (isArchiveImageFile(file)) { +
+ +
+ } @else { +
+ Preview not available for this file type. +
+ } +
+
diff --git a/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/submission-files-modal/submission-files-modal.component.scss b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/submission-files-modal/submission-files-modal.component.scss new file mode 100644 index 0000000000..dd674872c8 --- /dev/null +++ b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/submission-files-modal/submission-files-modal.component.scss @@ -0,0 +1,5 @@ +:host { + display: flex; + flex-direction: column; + height: 100%; +} diff --git a/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/submission-files-modal/submission-files-modal.component.ts b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/submission-files-modal/submission-files-modal.component.ts new file mode 100644 index 0000000000..a0068a7cde --- /dev/null +++ b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/submission-files-modal/submission-files-modal.component.ts @@ -0,0 +1,279 @@ +import {HttpResponse} from '@angular/common/http'; +import {Component, Inject, OnDestroy, OnInit} from '@angular/core'; +import * as monaco from 'monaco-editor'; +import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {OverseerAssessment} from 'src/app/api/models/doubtfire-model'; +import { + ArchiveFileEntry, + isArchiveCodeOrTextFile, + isArchiveImageFile, + isArchivePdfFile, +} from 'src/app/common/archive-viewer/archive-viewer.helpers'; +import {FileDownloaderService} from 'src/app/common/file-downloader/file-downloader.service'; +import {AlertService} from 'src/app/common/services/alert.service'; + +export interface SubmissionFilesModalData { + assessment: OverseerAssessment; + assessmentNumber?: number; + assessmentIsMostRecent?: boolean; + comparedWith?: OverseerAssessment; + comparedWithNumber?: number; + comparedWithIsMostRecent?: boolean; +} + +@Component({ + selector: 'f-submission-files-modal', + templateUrl: './submission-files-modal.component.html', + styleUrls: ['./submission-files-modal.component.scss'], +}) +export class SubmissionFilesModalComponent implements OnInit, OnDestroy { + private readonly diffOriginalUri = monaco.Uri.parse('inmemory://submission-compare/original'); + private readonly diffModifiedUri = monaco.Uri.parse('inmemory://submission-compare/modified'); + public readonly isArchiveCodeOrTextFile = isArchiveCodeOrTextFile; + public readonly isArchiveImageFile = isArchiveImageFile; + public readonly isArchivePdfFile = isArchivePdfFile; + + public archiveBlob: Blob | null = null; + public comparedArchiveBlob: Blob | null = null; + public isLoading = true; + public uploadRequirementNames: string[] = []; + public errorMessage: string | null = null; + public primarySelectedFile: ArchiveFileEntry | null = null; + public comparedSelectedFile: ArchiveFileEntry | null = null; + public primaryArchiveParsed = false; + public comparedArchiveParsed = false; + public selectedFilesMatch: boolean | null = null; + private selectedFilesComparisonToken = 0; + + public diffEditorOptions = { + theme: 'vs', + language: 'plaintext', + renderMinimap: false, + readOnly: true, + domReadOnly: true, + renderMarginRevertIcon: false, + enableSplitViewResizing: false, + useInlineViewWhenSpaceIsLimited: false, + renderSideBySideInlineBreakpoint: 1000, + renderSideBySide: true, + compactMode: false, + minimap: { + enabled: false, + }, + lineNumbers: 'off', + automaticLayout: true, + }; + public singleEditorOptions = { + theme: 'vs', + language: 'plaintext', + automaticLayout: true, + scrollBeyondLastLine: false, + renderMinimap: false, + readOnly: true, + minimap: { + enabled: false, + }, + }; + public primarySingleEditorOptions = {...this.singleEditorOptions}; + public comparedSingleEditorOptions = {...this.singleEditorOptions}; + public diffOriginalModel: {language: string; code: string; uri: monaco.Uri} = { + language: 'plaintext', + code: '', + uri: this.diffOriginalUri, + }; + public diffModifiedModel: {language: string; code: string; uri: monaco.Uri} = { + language: 'plaintext', + code: '', + uri: this.diffModifiedUri, + }; + + constructor( + private fileDownloader: FileDownloaderService, + private alerts: AlertService, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: SubmissionFilesModalData, + ) {} + + public get compareMode(): boolean { + return !!this.data.comparedWith; + } + + public get bothSelectionsReady(): boolean { + return !!this.primarySelectedFile && !!this.comparedSelectedFile; + } + + public get canShowDiffEditor(): boolean { + return ( + this.bothSelectionsReady && + isArchiveCodeOrTextFile(this.primarySelectedFile) && + isArchiveCodeOrTextFile(this.comparedSelectedFile) + ); + } + + ngOnInit(): void { + this.uploadRequirementNames = + this.data.assessment.task?.definition?.uploadRequirements?.map( + (requirement) => requirement?.name?.trim() ?? '', + ) ?? []; + + void this.loadSubmissionArchives(); + } + + ngOnDestroy(): void { + monaco.editor.getModel(this.diffOriginalUri)?.dispose(); + monaco.editor.getModel(this.diffModifiedUri)?.dispose(); + } + + private async loadSubmissionArchives(): Promise { + this.isLoading = true; + this.errorMessage = null; + this.primaryArchiveParsed = false; + this.comparedArchiveParsed = false; + this.primarySelectedFile = null; + this.comparedSelectedFile = null; + this.selectedFilesMatch = null; + this.resetDiffModels(); + this.resetSingleEditorOptions(); + + try { + if (this.data.comparedWith) { + this.archiveBlob = await this.downloadSubmissionArchive(this.data.assessment); + this.comparedArchiveBlob = await this.downloadSubmissionArchive(this.data.comparedWith); + } else { + this.archiveBlob = await this.downloadSubmissionArchive(this.data.assessment); + } + } catch (error) { + this.errorMessage = `Failed to load submission files: ${error}`; + this.alerts.error(this.errorMessage, 6000); + } finally { + this.isLoading = false; + } + } + + private downloadSubmissionArchive(assessment: OverseerAssessment): Promise { + return new Promise((resolve, reject) => { + this.fileDownloader.downloadBlob( + assessment.submissionFilesUrl(), + (_resourceUrl: string, response: HttpResponse) => { + if (!response.body) { + reject('No submission archive returned.'); + return; + } + resolve(response.body); + }, + (error) => { + reject(error?.error?.error ?? error); + }, + ); + }); + } + + public onPrimarySelectionChange(file: ArchiveFileEntry | null): void { + this.applySelectionChange(file, (value) => { + this.primarySelectedFile = value; + }); + } + + public onComparedSelectionChange(file: ArchiveFileEntry | null): void { + this.applySelectionChange(file, (value) => { + this.comparedSelectedFile = value; + }); + } + + private applySelectionChange( + file: ArchiveFileEntry | null, + assignSelection: (value: ArchiveFileEntry | null) => void, + ): void { + if (file && !file.isLoaded) { + return; + } + assignSelection(file); + this.refreshSingleEditorOptions(); + this.refreshDiffModels(); + void this.refreshSelectedFilesMatch(); + } + + public onPrimaryFilesLoaded(): void { + this.primaryArchiveParsed = true; + } + + public onComparedFilesLoaded(): void { + this.comparedArchiveParsed = true; + } + + private resetDiffModels(): void { + this.diffOriginalModel = {language: 'plaintext', code: '', uri: this.diffOriginalUri}; + this.diffModifiedModel = {language: 'plaintext', code: '', uri: this.diffModifiedUri}; + } + + private refreshDiffModels(): void { + if (!this.canShowDiffEditor) { + this.resetDiffModels(); + return; + } + + this.diffOriginalModel = { + language: this.primarySelectedFile?.language ?? 'plaintext', + code: this.primarySelectedFile?.textContent ?? '', + uri: this.diffOriginalUri, + }; + this.diffModifiedModel = { + language: this.comparedSelectedFile?.language ?? 'plaintext', + code: this.comparedSelectedFile?.textContent ?? '', + uri: this.diffModifiedUri, + }; + } + + private resetSingleEditorOptions(): void { + this.primarySingleEditorOptions = {...this.singleEditorOptions, language: 'plaintext'}; + this.comparedSingleEditorOptions = {...this.singleEditorOptions, language: 'plaintext'}; + } + + private refreshSingleEditorOptions(): void { + this.primarySingleEditorOptions = { + ...this.singleEditorOptions, + language: this.primarySelectedFile?.language ?? 'plaintext', + }; + this.comparedSingleEditorOptions = { + ...this.singleEditorOptions, + language: this.comparedSelectedFile?.language ?? 'plaintext', + }; + } + + private async refreshSelectedFilesMatch(): Promise { + const token = ++this.selectedFilesComparisonToken; + if (!this.primarySelectedFile || !this.comparedSelectedFile) { + this.selectedFilesMatch = null; + return; + } + + const [primaryHash, comparedHash] = await Promise.all([ + this.computeFileHash(this.primarySelectedFile), + this.computeFileHash(this.comparedSelectedFile), + ]); + + if (token !== this.selectedFilesComparisonToken) { + return; + } + + this.selectedFilesMatch = primaryHash === comparedHash; + } + + private async computeFileHash(file: ArchiveFileEntry): Promise { + let bytes: Uint8Array | null = file.data ?? null; + if (!bytes && file.textContent !== undefined) { + bytes = new TextEncoder().encode(file.textContent); + } + if (!bytes && file.blob) { + bytes = new Uint8Array(await file.blob.arrayBuffer()); + } + if (!bytes) { + return ''; + } + + const digest = await crypto.subtle.digest('SHA-256', bytes); + return Array.from(new Uint8Array(digest)) + .map((value) => value.toString(16).padStart(2, '0')) + .join(''); + } +} diff --git a/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/task-overseer-report.component.html b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/task-overseer-report.component.html index 5609881791..2773aed285 100644 --- a/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/task-overseer-report.component.html +++ b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/task-overseer-report.component.html @@ -12,10 +12,13 @@ - Submission {{ idx + 1 }}: {{ oa.timestamp | humanizedDate }} + Submission {{ overseerAssessments.length - idx }}: {{ oa.timestamp | humanizedDate }} @if (idx === 0) { (Most recent) } + @if (isComparisonSource(oa)) { + (Selected for comparison) + } @if (oa.reportReady) {
@@ -25,6 +28,43 @@ } @else { cancel } + @if (oa.hasSubmissionFiles && currentUnitRole) { + +
+ + + @if (hasComparisonSourceFor(oa)) { + + } @else if (isComparisonSource(oa)) { + + } @else { + + } + + }
} @else {
diff --git a/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/task-overseer-report.component.ts b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/task-overseer-report.component.ts index cf4c5a86ed..e576813299 100644 --- a/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/task-overseer-report.component.ts +++ b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-overseer-report/task-overseer-report.component.ts @@ -1,10 +1,13 @@ import {Component, Input, OnInit} from '@angular/core'; -import {OverseerAssessment} from 'src/app/api/models/doubtfire-model'; +import {MatDialog} from '@angular/material/dialog'; +import {MatMenuTrigger} from '@angular/material/menu'; +import {OverseerAssessment, UnitRole, UserService} from 'src/app/api/models/doubtfire-model'; import {Task} from 'src/app/api/models/task'; import {OverseerAssessmentService} from 'src/app/api/services/overseer-assessment.service'; import {OverseerStepResultService} from 'src/app/api/services/overseer-step-result.service'; import {AlertService} from 'src/app/common/services/alert.service'; import {TaskSubmissionService} from 'src/app/common/services/task-submission.service'; +import {SubmissionFilesModalComponent} from './submission-files-modal/submission-files-modal.component'; @Component({ selector: 'f-task-overseer-report', @@ -14,14 +17,22 @@ import {TaskSubmissionService} from 'src/app/common/services/task-submission.ser export class TaskOverseerReportComponent implements OnInit { @Input() task: Task; @Input() loadOverseerAssessmentId?: number; + public comparisonSourceAssessmentId: number | null = null; constructor( private alerts: AlertService, private submissions: TaskSubmissionService, private overseerAssessmentService: OverseerAssessmentService, private overseerStepResultsService: OverseerStepResultService, + private dialog: MatDialog, + private userService: UserService, ) {} + public get currentUnitRole(): UnitRole | undefined { + const currentUser = this.userService.currentUser; + return this.task.unit.staff.find((ur) => ur.user.id === currentUser.id); + } + public viewOutput: 'your_output' | 'expected_output' | 'diff' | 'split_diff' = 'your_output'; stdoutOptions = { @@ -88,6 +99,17 @@ export class TaskOverseerReportComponent implements OnInit { public overseerAssessments: OverseerAssessment[] = []; + public get comparisonSourceAssessment(): OverseerAssessment | null { + if (!this.comparisonSourceAssessmentId) { + return null; + } + return ( + this.overseerAssessments.find( + (assessment) => assessment.id === this.comparisonSourceAssessmentId, + ) ?? null + ); + } + ngOnInit(): void { this.loadAssessments(); } @@ -99,6 +121,14 @@ export class TaskOverseerReportComponent implements OnInit { this.overseerAssessmentService.queryForTask(this.task).subscribe({ next: (assessments) => { this.overseerAssessments = assessments; + if ( + this.comparisonSourceAssessmentId && + !this.overseerAssessments.some( + (assessment) => assessment.id === this.comparisonSourceAssessmentId, + ) + ) { + this.comparisonSourceAssessmentId = null; + } for (const oa of this.overseerAssessments) { for (const result of oa.stepResultsCache.currentValues) { result.overseerStep = this.task.definition.overseerStepsCache.currentValues.find( @@ -142,4 +172,77 @@ export class TaskOverseerReportComponent implements OnInit { }, }); } + + viewSubmissionOptions(event: Event) { + event.stopPropagation(); + } + + isComparisonSource(assessment: OverseerAssessment): boolean { + return this.comparisonSourceAssessmentId === assessment.id; + } + + hasComparisonSourceFor(assessment: OverseerAssessment): boolean { + return ( + this.comparisonSourceAssessmentId !== null && + this.comparisonSourceAssessmentId !== assessment.id + ); + } + + selectComparisonSource( + assessment: OverseerAssessment, + event?: Event, + menuTrigger?: MatMenuTrigger, + ) { + event?.stopPropagation(); + this.comparisonSourceAssessmentId = assessment.id; + menuTrigger?.closeMenu(); + this.alerts.message(`Selected submission ${assessment.timestampString} for comparison.`, 3500); + } + + clearComparisonSource(event?: Event) { + event?.stopPropagation(); + this.comparisonSourceAssessmentId = null; + } + + compareWithSelected(assessment: OverseerAssessment, event?: Event) { + event?.stopPropagation(); + const selected = this.comparisonSourceAssessment; + if (!selected || selected.id === assessment.id) { + return; + } + + this.openSubmissionFilesDialog(assessment, selected); + } + + viewSubmissionFiles(assessment: OverseerAssessment, event?: Event) { + event?.stopPropagation(); + this.openSubmissionFilesDialog(assessment); + } + + private openSubmissionFilesDialog( + assessment: OverseerAssessment, + comparedWith?: OverseerAssessment, + ) { + const assessmentIndex = this.overseerAssessments.findIndex((item) => item.id === assessment.id); + const comparedWithIndex = comparedWith + ? this.overseerAssessments.findIndex((item) => item.id === comparedWith.id) + : -1; + + this.dialog.open(SubmissionFilesModalComponent, { + data: { + assessment, + assessmentNumber: + assessmentIndex >= 0 ? this.overseerAssessments.length - assessmentIndex : undefined, + assessmentIsMostRecent: assessmentIndex === 0, + comparedWith, + comparedWithNumber: + comparedWithIndex >= 0 ? this.overseerAssessments.length - comparedWithIndex : undefined, + comparedWithIsMostRecent: comparedWithIndex === 0, + }, + maxWidth: '95vw', + width: '100%', + height: '90vh', + panelClass: 'submission-files-dialog', + }); + } } diff --git a/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-status-card/task-status-card.component.ts b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-status-card/task-status-card.component.ts index d08c1b1ffd..8738a0bffd 100644 --- a/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-status-card/task-status-card.component.ts +++ b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-status-card/task-status-card.component.ts @@ -55,7 +55,15 @@ export class TaskStatusCardComponent implements OnChanges, AfterViewInit { reapplyTriggers(): void { // if tutor is in queryParam if (this.router.globals.params.tutor != null) { - this.triggers = this.taskService.statusKeys.map((k) => this.taskService.statusData(k)); + this.triggers = this.taskService.statusKeys + .map((k) => this.taskService.statusData(k)) + .filter((trigger) => { + if (trigger.status !== 'complete') { + return true; + } + + return this.task.canMarkComplete || this.task.status === 'complete'; + }); } else { const studentTriggers = _.map( this.taskService.switchableStates.student as TaskStatusEnum[], @@ -80,6 +88,10 @@ export class TaskStatusCardComponent implements OnChanges, AfterViewInit { } triggerTransition(trigger: TaskStatusEnum): void { + if (trigger === 'complete' && !this.task.canMarkComplete) { + return; + } + if (trigger === 'ready_for_feedback') { this.uploadSubmission(); } else { diff --git a/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-submission-card/task-submission-card.component.html b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-submission-card/task-submission-card.component.html index 7d1fd9f9b9..368d4b8343 100644 --- a/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-submission-card/task-submission-card.component.html +++ b/src/app/projects/states/dashboard/directives/task-dashboard/directives/task-submission-card/task-submission-card.component.html @@ -11,10 +11,12 @@ You can choose to download your previous submission or the files you uploaded below.

-

- You uploaded this submission {{ task.submissionDate | date: 'dd/MM/yyyy' }}. -

+ @if (submission?.isUploaded && task.submissionDate) { +

+ You uploaded this submission {{ task.submissionDate | date: 'dd/MM/yyyy' }}. +

+ }

If you feel there has been an error in your submission, you can request to regenerate your submission under the "Actions" dropdown menu. diff --git a/src/app/projects/states/tutor-discussion/tutor-discussion.component.html b/src/app/projects/states/tutor-discussion/tutor-discussion.component.html index 9c262fcede..97c5d30809 100644 --- a/src/app/projects/states/tutor-discussion/tutor-discussion.component.html +++ b/src/app/projects/states/tutor-discussion/tutor-discussion.component.html @@ -190,7 +190,7 @@

{{ task.definition.name }}

+ } +
+ + +
+
+

Task Details

Name the task and set target grade

-
- + + - - Task Learning Outcomes -
+
+

Task Learning Outcomes

Add learning outcomes for this task

-
-
+ + - - Inbox -
+
+

Inbox

Who assesses {{ unit.hasGroupwork() ? 'and submits ' : '' }}this task?

-
-
+ + - - Due Dates -
+
+

Due Dates

When is the task due?

-
-
+ + - - Upload Requirements -
+
+

Upload Requirements

What do students need to upload?

-
-
+ + - - Task Resources -
+
+

Task Resources

Upload task descriptions and resources

-
-
+ + - - Prerequisite Tasks -
+
+

Prerequisite Tasks

Select which tasks need to be submitted before {{ taskDefinition.abbreviation }} {{ taskDefinition.name }} can be submitted

- -
-
+ + - - Discussion Prompts -
+
+

Discussion Prompts

Discussion prompts for tutors to use when discussing student tasks in class

- -
-
+ + - @if (overseerEnabled) { - - Task Assessment Automation -
+ @if (overseerEnabled) { +
+

Task Assessment Automation

Configure automated assessment

-
-
- } + + + } - - SCORM Test -
+
+

SCORM Test

Upload the corresponding SCORM 2004 test (e.g. Numbas)

-
-
+ + - - Optional Settings -
+
+

Optional Settings

Apply other options

-
-
- -
-
+ + + + + +
+
+
+
diff --git a/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-editor.component.scss b/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-editor.component.scss index 82dc723d88..e69de29bb2 100644 --- a/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-editor.component.scss +++ b/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-editor.component.scss @@ -1,15 +0,0 @@ -.mat-toolbar { - background-color: white; - // position: fixed; - // bottom: 0; - // width: 100%; -} - -.form-group { - -} - -#task-def-head { - background-color: white; - z-index: 10; -} diff --git a/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-editor.component.ts b/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-editor.component.ts index d77a3d5ad8..1da5af76f1 100644 --- a/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-editor.component.ts +++ b/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-editor.component.ts @@ -1,20 +1,73 @@ -import {Component, Input, OnInit} from '@angular/core'; +import { + AfterViewInit, + Component, + ElementRef, + HostListener, + Input, + OnChanges, + OnDestroy, + OnInit, + QueryList, + SimpleChanges, + ViewChild, + ViewChildren, +} from '@angular/core'; +import {Subscription} from 'rxjs'; import {TaskDefinition} from 'src/app/api/models/task-definition'; import {Unit} from 'src/app/api/models/unit'; import {TaskDefinitionService} from 'src/app/api/services/task-definition.service'; import {AlertService} from 'src/app/common/services/alert.service'; import {DoubtfireConstants} from 'src/app/config/constants/doubtfire-constants'; +type TaskDefinitionSectionId = + | 'task-details' + | 'task-learning-outcomes' + | 'inbox' + | 'due-dates' + | 'upload-requirements' + | 'task-resources' + | 'prerequisite-tasks' + | 'discussion-prompts' + | 'task-assessment-automation' + | 'scorm-test' + | 'optional-settings'; + +interface TaskDefinitionSection { + id: TaskDefinitionSectionId; + label: string; +} + @Component({ selector: 'f-task-definition-editor', templateUrl: 'task-definition-editor.component.html', styleUrls: ['task-definition-editor.component.scss'], }) -export class TaskDefinitionEditorComponent implements OnInit { +export class TaskDefinitionEditorComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy { @Input() taskDefinition: TaskDefinition; @Input() unit: Unit; + @ViewChild('sectionScrollContainer') sectionScrollContainer: ElementRef; + @ViewChildren('sectionElement') sectionElements: QueryList>; public overseerEnabled: boolean = false; + public activeSectionId: TaskDefinitionSectionId = 'task-details'; + public readonly sectionList: TaskDefinitionSection[] = [ + {id: 'task-details', label: 'Task Details'}, + {id: 'task-learning-outcomes', label: 'Task Learning Outcomes'}, + {id: 'inbox', label: 'Inbox'}, + {id: 'due-dates', label: 'Due Dates'}, + {id: 'upload-requirements', label: 'Upload Requirements'}, + {id: 'task-resources', label: 'Task Resources'}, + {id: 'prerequisite-tasks', label: 'Prerequisite Tasks'}, + {id: 'discussion-prompts', label: 'Discussion Prompts'}, + {id: 'task-assessment-automation', label: 'Task Assessment Automation'}, + {id: 'scorm-test', label: 'SCORM Test'}, + {id: 'optional-settings', label: 'Optional Settings'}, + ]; + + private sectionElementMap = new Map(); + private sectionChangesSubscription?: Subscription; + private overseerEnabledSubscription?: Subscription; + private readonly scrollTopOffsetPx: number = 112; constructor( private taskDefinitionService: TaskDefinitionService, @@ -23,9 +76,94 @@ export class TaskDefinitionEditorComponent implements OnInit { ) {} public ngOnInit() { - this.constants.IsOverseerEnabled.subscribe((enabled) => { + this.overseerEnabledSubscription = this.constants.IsOverseerEnabled.subscribe((enabled) => { this.overseerEnabled = enabled && this.unit.overseerEnabled; + this.ensureActiveSectionIsVisible(); + this.rebuildSectionElementMap(); + }); + } + + public ngAfterViewInit() { + this.rebuildSectionElementMap(); + this.sectionChangesSubscription = this.sectionElements.changes.subscribe(() => { + this.rebuildSectionElementMap(); + this.syncActiveSectionOnScroll(); }); + queueMicrotask(() => this.syncActiveSectionOnScroll()); + } + + public ngOnChanges(changes: SimpleChanges) { + if (changes.taskDefinition && !changes.taskDefinition.firstChange) { + this.ensureActiveSectionIsVisible(); + queueMicrotask(() => this.syncActiveSectionOnScroll()); + } + } + + public ngOnDestroy() { + this.sectionChangesSubscription?.unsubscribe(); + this.overseerEnabledSubscription?.unsubscribe(); + } + + public get visibleSections(): TaskDefinitionSection[] { + return this.overseerEnabled + ? this.sectionList + : this.sectionList.filter((section) => section.id !== 'task-assessment-automation'); + } + + public scrollToSection(sectionId: TaskDefinitionSectionId) { + this.activeSectionId = sectionId; + + const container = this.sectionScrollContainer?.nativeElement; + const target = this.sectionElementMap.get(sectionId); + + if (!container || !target) return; + + if (this.isContainerScrollable(container)) { + const targetTop = this.getSectionTopInContainer(container, target); + container.scrollTo({ + top: Math.max(targetTop - 8, 0), + behavior: 'smooth', + }); + return; + } + + target.scrollIntoView({ + behavior: 'smooth', + block: 'start', + }); + } + + public syncActiveSectionOnScroll() { + const container = this.sectionScrollContainer?.nativeElement; + if (!container) return; + + if (!this.isContainerScrollable(container)) { + this.syncActiveSectionOnWindowScroll(); + return; + } + + const scrollPosition = container.scrollTop + 48; + let nextActiveSection = this.visibleSections[0]?.id; + + this.visibleSections.forEach((section) => { + const sectionElement = this.sectionElementMap.get(section.id); + if ( + sectionElement && + this.getSectionTopInContainer(container, sectionElement) <= scrollPosition + ) { + nextActiveSection = section.id; + } + }); + + if (nextActiveSection) { + this.activeSectionId = nextActiveSection; + } + } + + @HostListener('window:scroll') + @HostListener('window:resize') + public onWindowScroll() { + this.syncActiveSectionOnScroll(); } public save() { @@ -37,4 +175,49 @@ export class TaskDefinitionEditorComponent implements OnInit { error: (message) => this.alerts.error(message), }); } + + private ensureActiveSectionIsVisible() { + if (!this.visibleSections.some((section) => section.id === this.activeSectionId)) { + this.activeSectionId = this.visibleSections[0]?.id ?? 'task-details'; + } + } + + private rebuildSectionElementMap() { + this.sectionElementMap.clear(); + + this.sectionElements?.forEach((sectionElementRef) => { + const nativeElement = sectionElementRef.nativeElement; + const sectionId = nativeElement.getAttribute('data-section-id') as TaskDefinitionSectionId; + + if (sectionId) { + this.sectionElementMap.set(sectionId, nativeElement); + } + }); + } + + private getSectionTopInContainer(container: HTMLElement, sectionElement: HTMLElement): number { + const containerRect = container.getBoundingClientRect(); + const sectionRect = sectionElement.getBoundingClientRect(); + return container.scrollTop + (sectionRect.top - containerRect.top); + } + + private syncActiveSectionOnWindowScroll() { + const threshold = this.scrollTopOffsetPx + 12; + let nextActiveSection = this.visibleSections[0]?.id; + + this.visibleSections.forEach((section) => { + const sectionElement = this.sectionElementMap.get(section.id); + if (sectionElement && sectionElement.getBoundingClientRect().top <= threshold) { + nextActiveSection = section.id; + } + }); + + if (nextActiveSection) { + this.activeSectionId = nextActiveSection; + } + } + + private isContainerScrollable(container: HTMLElement): boolean { + return container.scrollHeight > container.clientHeight + 1; + } } diff --git a/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-options/task-definition-options.component.html b/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-options/task-definition-options.component.html index 6ef203ae27..5d00855580 100644 --- a/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-options/task-definition-options.component.html +++ b/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-options/task-definition-options.component.html @@ -24,6 +24,16 @@
+
+ + Requires Discussion + + + Tutors cannot mark this task as complete unless it has first been marked as discussed in + class. + +
+
Graded @if (taskDefinition.hasTaskAssessmentResources) { -
- +
+ + @if (showOverseerResourcesEditor) { +
+ @if (isLoadingOverseerResourcesArchive) { +
+ Loading Overseer Resources archive... +
+ } @else if (overseerResourcesArchive) { + + } @else { +
+ Unable to load Overseer Resources archive. +
+ } +
+ } }
diff --git a/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-overseer/task-definition-overseer.component.ts b/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-overseer/task-definition-overseer.component.ts index 1c188080ed..1d539cce29 100644 --- a/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-overseer/task-definition-overseer.component.ts +++ b/src/app/units/states/edit/directives/unit-tasks-editor/task-definition-editor/task-definition-overseer/task-definition-overseer.component.ts @@ -1,4 +1,5 @@ import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop'; +import {HttpClient, HttpResponse} from '@angular/common/http'; import {Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild} from '@angular/core'; import {Observable} from 'rxjs'; import { @@ -47,7 +48,11 @@ export class TaskDefinitionOverseerComponent implements OnChanges, OnInit { public stepType: 'status_check' | 'output_diff' = 'status_check'; public visibility = 'public'; + public showOverseerResourcesEditor = false; + public isLoadingOverseerResourcesArchive = false; + public overseerResourcesArchive: Blob | File | null = null; constructor( + private http: HttpClient, private alerts: AlertService, private overseerImageService: OverseerImageService, private modalService: TaskAssessmentModalService, @@ -264,6 +269,9 @@ export class TaskDefinitionOverseerComponent implements OnChanges, OnInit { this.taskDefinition.overseerStepsCache.values.subscribe((steps) => { this.overseerSteps = [...steps]; }); + this.showOverseerResourcesEditor = false; + this.isLoadingOverseerResourcesArchive = false; + this.overseerResourcesArchive = null; } } @@ -304,6 +312,8 @@ export class TaskDefinitionOverseerComponent implements OnChanges, OnInit { next: () => { this.alerts.success('Deleted Overseer Resources', 2000); this.taskDefinition.hasTaskAssessmentResources = false; + this.showOverseerResourcesEditor = false; + this.overseerResourcesArchive = null; }, }); } @@ -326,6 +336,7 @@ export class TaskDefinitionOverseerComponent implements OnChanges, OnInit { this.alerts.success('Uploaded Overseer Resources', 2000); this.taskDefinition.hasTaskAssessmentResources = true; this.taskDefinition.overseerResourceFiles = [...resourceFiles]; + this.overseerResourcesArchive = file; }, error: (message) => this.alerts.error(message, 6000), }); @@ -333,4 +344,42 @@ export class TaskDefinitionOverseerComponent implements OnChanges, OnInit { this.alerts.error('Please drop a zip with scripts for this task to upload', 6000); } } + + public toggleOverseerResourcesEditor() { + this.showOverseerResourcesEditor = !this.showOverseerResourcesEditor; + if ( + this.showOverseerResourcesEditor && + !this.overseerResourcesArchive && + this.taskDefinition?.hasTaskAssessmentResources + ) { + this.loadOverseerResourcesArchive(); + } + } + + public onOverseerResourcesArchiveSaved(response: HttpResponse) { + this.taskDefinition.hasTaskAssessmentResources = true; + + if (Array.isArray(response.body)) { + this.taskDefinition.overseerResourceFiles = response.body.filter( + (file): file is string => typeof file === 'string', + ); + } + } + + private loadOverseerResourcesArchive() { + this.isLoadingOverseerResourcesArchive = true; + this.http.get(this.taskDefinition.getOverseerResourcesUrl(), {responseType: 'blob'}).subscribe({ + next: (archiveBlob) => { + this.overseerResourcesArchive = archiveBlob; + }, + error: (error) => { + this.isLoadingOverseerResourcesArchive = false; + this.showOverseerResourcesEditor = false; + this.alerts.error(`Failed to load Overseer Resources Zip: ${error?.error?.error ?? error}`); + }, + complete: () => { + this.isLoadingOverseerResourcesArchive = false; + }, + }); + } } diff --git a/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.html b/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.html index 33f48afcd9..82562925c1 100644 --- a/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.html +++ b/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.html @@ -1,231 +1,263 @@ -
-
-
-
-

Task List

-

Plan the list of tasks for students to complete.

+
+
+
+

Task List

+

Plan the list of tasks for students to complete.

+
+
+
+ +
+ + +
- -
- - - +
+ +
+ + +
+
+
+ @if (unit.allowFlexibleDates) { + + } + +
+ + @if (unit.allowFlexibleDates) { + @if (manageDueDates) { +
+ +
+
+

Manage Target Dates

+

Modify or add target dates for each target grade

+
+
+ + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Task + {{ td.abbreviation }} {{ td.name }} + +
+ +
+
+
+ + Start Date + + + + + + + Target Date + + @if (isStartAfterTarget(td, g)) { + Target date must be after start date + } - + + + + + + + + +
+
+ } + } + +
+
Task Name - {{ taskDefinition.abbreviation }} {{ taskDefinition.name }} - Grade +
- {{ taskDefinition.targetGradeText }} -
Start Date - {{ taskDefinition.startDate | date: 'd LLL y' }} - Due Date - {{ taskDefinition.targetDate | date: 'd LLL y' }} - Deadline - {{ taskDefinition.dueDate | date: 'd LLL y' }} - - @if (taskDefinitionHasChanges(taskDefinition)) { +
- } - -
- - - - - - - - - - - - @if (unit.allowFlexibleDates) { - - } - - - @if (selectedTaskDefinition) { - - } -
-
- -@if (unit.allowFlexibleDates) { - @if (manageDueDates) { -
- -
-
-

Manage Target Dates

-

Modify or add target dates for each target grade

-
-
- - - - - - - - - - + @if (!isTaskListCollapsed) { +
+

+ {{ taskDefinition.name }} +

+
+ {{ gradeNames[taskDefinition.targetGrade] }} Task +
+
- - -
Task - {{ td.abbreviation }} {{ td.name }} - -
- -
-
-
- - Start Date + search + +
+
+ } +
+ - - - - - Target Date - + + @if ((taskDefinitionSource?.filteredData ?? []).length === 0) { +
No task definitions to display
+ } + @for ( + taskDefinition of taskDefinitionSource?.filteredData ?? []; + track taskDefinition + ) { + +
- @if (isStartAfterTarget(td, g)) { - Target date must be after start date - } + > +
+
+ - - - -
-
+
+ @if (taskDefinitionHasChanges(taskDefinition)) { + + } + +
+ } +
+
+
+ + } + +
+
+ + +
+ @if (selectedTaskDefinition) { + + } @else { +
+

Select a task definition

+

Pick a task from the left list to edit its details.

+
+ }
- } -} +
+
diff --git a/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.scss b/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.scss index e33b519c28..da9f77f5b0 100644 --- a/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.scss +++ b/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.scss @@ -1,10 +1,3 @@ -.mat-toolbar { - background-color: white; - // position: fixed; - // bottom: 0; - // width: 100%; -} - ::ng-deep input.mat-mdc-input-element.fallback-date { font-style: italic; color: #bfbfbf !important; diff --git a/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.ts b/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.ts index 933a346d5d..f40f563a7c 100644 --- a/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.ts +++ b/src/app/units/states/edit/directives/unit-tasks-editor/unit-task-editor.component.ts @@ -1,7 +1,5 @@ -import {AfterViewInit, Component, Inject, Input, ViewChild} from '@angular/core'; -import {MatPaginator} from '@angular/material/paginator'; -import {MatSort, Sort} from '@angular/material/sort'; -import {MatTable, MatTableDataSource} from '@angular/material/table'; +import {AfterViewInit, Component, Inject, Input} from '@angular/core'; +import {MatTableDataSource} from '@angular/material/table'; import {addWeeks} from 'date-fns'; import {Subscription} from 'rxjs'; import { @@ -9,6 +7,7 @@ import { csvResultModalService, csvUploadModalService, } from 'src/app/ajs-upgraded-providers'; +import {Grade} from 'src/app/api/models/grade'; import {TaskDefinition} from 'src/app/api/models/task-definition'; import {Unit} from 'src/app/api/models/unit'; import {FeedbackTemplateService} from 'src/app/api/services/feedback-template.service'; @@ -23,23 +22,12 @@ type GradeCol = 'p' | 'c' | 'd' | 'hd'; styleUrls: ['unit-task-editor.component.scss'], }) export class UnitTaskEditorComponent implements AfterViewInit { - @ViewChild(MatTable, {static: false}) table: MatTable; - @ViewChild(MatSort, {static: false}) sort: MatSort; - @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; - @Input() unit: Unit; public taskDefinitionSource: MatTableDataSource; - public columns: string[] = [ - 'name', - 'grade', - 'startDate', - 'targetDate', - 'deadlineDate', - 'taskDefAction', - ]; public filter: string; public selectedTaskDefinition: TaskDefinition; + public isTaskListCollapsed: boolean = false; public gradeColumns: string[] = ['p', 'c', 'd', 'hd']; public dueDateColumns: string[] = ['taskDefinition', 'p', 'c', 'd', 'hd']; @@ -47,6 +35,8 @@ export class UnitTaskEditorComponent implements AfterViewInit { public manageDueDates: boolean; + protected gradeNames: string[] = Grade.GRADES; + isStartAfterTarget(td: TaskDefinition, g: GradeCol): boolean { const start = this.getGradeStartDate(td, g); const target = this.getGradeDueDate(td, g); @@ -158,8 +148,6 @@ export class UnitTaskEditorComponent implements AfterViewInit { this.subscriptions.push( this.unit.taskDefinitionCache.values.subscribe((taskDefinitions) => { this.taskDefinitionSource = new MatTableDataSource(taskDefinitions); - this.taskDefinitionSource.paginator = this.paginator; - this.taskDefinitionSource.sort = this.sort; this.taskDefinitionSource.filterPredicate = (data: any, filter: string) => data.matches(filter); }), @@ -185,62 +173,37 @@ export class UnitTaskEditorComponent implements AfterViewInit { public selectTaskDefinition(taskDefinition: TaskDefinition) { if (this.selectedTaskDefinition === taskDefinition) { - this.selectedTaskDefinition = null; - } else { - this.selectedTaskDefinition = taskDefinition; - - // Record original save data if none present - if (!this.selectedTaskDefinition.hasOriginalSaveData) { - this.selectedTaskDefinition.setOriginalSaveData(this.taskDefinitionService.mapping); - } - - this.feedbackTemplateService - .query({contextType: 'task_definitions', contextId: this.selectedTaskDefinition.id}, {}) - .subscribe({ - error: () => this.alerts.error('Error loading task feedback templates.'), - }); + return; } - } - public sortData(sort: Sort) { - const data = this.taskDefinitionSource.data; + this.selectedTaskDefinition = taskDefinition; - if (!sort.active || sort.direction === '') { - this.taskDefinitionSource.data = data; - return; + // Record original save data if none present + if (!this.selectedTaskDefinition.hasOriginalSaveData) { + this.selectedTaskDefinition.setOriginalSaveData(this.taskDefinitionService.mapping); } - this.taskDefinitionSource.data = data.sort((a, b) => { - const isAsc = sort.direction === 'asc'; - switch (sort.active) { - case 'name': - return this.compare(a.abbreviation, b.abbreviation, isAsc); - case 'grade': - return this.compare(a.targetGrade, b.targetGrade, isAsc); - case 'startDate': - return this.compare(a.startDate.getTime(), b.startDate.getTime(), isAsc); - case 'targetDate': - return this.compare(a.targetDate.getTime(), b.targetDate.getTime(), isAsc); - case 'deadlineDate': - return this.compare(a.dueDate.getTime(), b.dueDate.getTime(), isAsc); - default: - return 0; - } - }); + this.feedbackTemplateService + .query({contextType: 'task_definitions', contextId: this.selectedTaskDefinition.id}, {}) + .subscribe({ + error: () => this.alerts.error('Error loading task feedback templates.'), + }); + } + + public isSelectedTaskDefinition(taskDefinition: TaskDefinition): boolean { + return this.selectedTaskDefinition === taskDefinition; } - public compare(a: number | string, b: number | string, isAsc: boolean): number { - return (a < b ? -1 : 1) * (isAsc ? 1 : -1); + public toggleTaskListCollapsed(): void { + this.isTaskListCollapsed = !this.isTaskListCollapsed; } applyFilter(filterValue: string) { + if (!this.taskDefinitionSource) return; + this.taskDefinitionSource.filter = filterValue.trim().toLowerCase(); this.selectedTaskDefinition = null; - - if (this.taskDefinitionSource.paginator) { - this.taskDefinitionSource.paginator.firstPage(); - } } private guessTaskAbbreviation() { diff --git a/src/app/units/states/edit/edit.tpl.html b/src/app/units/states/edit/edit.tpl.html index 8a0dc37d52..aae61b19f2 100644 --- a/src/app/units/states/edit/edit.tpl.html +++ b/src/app/units/states/edit/edit.tpl.html @@ -1,4 +1,4 @@ -
+
{{tab.title}} diff --git a/src/app/units/states/portfolios/portfolios.tpl.html b/src/app/units/states/portfolios/portfolios.tpl.html index eaff240d73..8625891202 100644 --- a/src/app/units/states/portfolios/portfolios.tpl.html +++ b/src/app/units/states/portfolios/portfolios.tpl.html @@ -1,4 +1,4 @@ -
+ +} diff --git a/src/app/units/states/tasks/inbox/inbox.component.ts b/src/app/units/states/tasks/inbox/inbox.component.ts index 9f2bd164ab..554222dd75 100644 --- a/src/app/units/states/tasks/inbox/inbox.component.ts +++ b/src/app/units/states/tasks/inbox/inbox.component.ts @@ -55,6 +55,10 @@ export class InboxComponent implements OnInit, OnDestroy { return this.inboxPanel?.nativeElement.getBoundingClientRect().width < 150; } + get isMobileView(): boolean { + return this.mediaObserver.isActive('lt-md'); + } + constructor( private hotkeys: HotkeysService, private selectedTask: SelectedTaskService, @@ -102,9 +106,18 @@ export class InboxComponent implements OnInit, OnDestroy { keys: 'control.Shift.c', description: 'Mark selected task as complete', }) - .subscribe(() => - this.selectedTask.selectedTask?.updateTaskStatus('complete') - ); + .subscribe(() => { + const task = this.selectedTask.selectedTask; + if (!task) { + return; + } + + if (!task.canMarkComplete) { + return; + } + + task.updateTaskStatus('complete'); + }); } if (!registeredHotkeys.includes('control.shift.d')) { diff --git a/src/app/units/states/tasks/viewer/directives/f-unit-task-list/f-unit-task-list.component.html b/src/app/units/states/tasks/viewer/directives/f-unit-task-list/f-unit-task-list.component.html index 1ca78334f0..e5175a16a0 100644 --- a/src/app/units/states/tasks/viewer/directives/f-unit-task-list/f-unit-task-list.component.html +++ b/src/app/units/states/tasks/viewer/directives/f-unit-task-list/f-unit-task-list.component.html @@ -37,7 +37,7 @@ >
- +

{{ task.name }}