From f577236548dae34b635b9f596da29fcaffc8cf7e Mon Sep 17 00:00:00 2001 From: hudson-newey Date: Thu, 20 Jun 2024 07:58:36 +1000 Subject: [PATCH 001/106] Created verification interface --- angular.json | 12 +- package-lock.json | 345 +++++++++++++++++- package.json | 1 + src/app/app.module.ts | 5 +- .../pages/details/details.component.ts | 2 + .../pages/details/details.component.ts | 2 + .../sites/pages/details/details.component.ts | 3 + .../annotation-search-preview.component.html | 19 + .../annotation-search-preview.component.scss | 4 + ...nnotation-search-preview.component.spec.ts | 0 .../annotation-search-preview.component.ts | 85 +++++ .../verification/pages/new/new.component.html | 55 +++ .../verification/pages/new/new.component.scss | 0 .../pages/new/new.component.spec.ts | 0 .../verification/pages/new/new.component.ts | 171 +++++++++ .../pages/view/view.component.html | 32 ++ .../pages/view/view.component.scss | 3 + .../pages/view/view.component.spec.ts | 0 .../verification/pages/view/view.component.ts | 38 ++ .../verification/verification.menu.ts | 96 +++++ .../verification/verification.module.ts | 30 ++ .../verification/verification.routes.ts | 37 ++ src/app/services/theme/theme.service.ts | 49 +++ src/index.html | 3 + 24 files changed, 982 insertions(+), 10 deletions(-) create mode 100644 src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.html create mode 100644 src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.scss create mode 100644 src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.spec.ts create mode 100644 src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.ts create mode 100644 src/app/components/verification/pages/new/new.component.html create mode 100644 src/app/components/verification/pages/new/new.component.scss create mode 100644 src/app/components/verification/pages/new/new.component.spec.ts create mode 100644 src/app/components/verification/pages/new/new.component.ts create mode 100644 src/app/components/verification/pages/view/view.component.html create mode 100644 src/app/components/verification/pages/view/view.component.scss create mode 100644 src/app/components/verification/pages/view/view.component.spec.ts create mode 100644 src/app/components/verification/pages/view/view.component.ts create mode 100644 src/app/components/verification/verification.menu.ts create mode 100644 src/app/components/verification/verification.module.ts create mode 100644 src/app/components/verification/verification.routes.ts diff --git a/angular.json b/angular.json index d80786940..dddc4a9f9 100644 --- a/angular.json +++ b/angular.json @@ -38,7 +38,9 @@ "node_modules" ] }, - "scripts": [], + "scripts": [ + "node_modules/@ecoacoustics/web-components/components.js" + ], "sourceMap": true, "allowedCommonJsDependencies": [ "zone.js/dist/zone-error", @@ -83,7 +85,13 @@ "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "buildTarget": "workbench-client:build" + "buildTarget": "workbench-client:build", + "headers": { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", + "Cross-Origin-Resource-Policy": "cross-origin", + "Access-Control-Allow-Origin": "*" + } }, "configurations": { "production": { diff --git a/package-lock.json b/package-lock.json index acfdc8f05..38dad82cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@angular/platform-server": "17.1.2", "@angular/router": "17.1.2", "@angular/ssr": "^17.1.2", + "@ecoacoustics/web-components": "^0.1.1-alpha.4", "@fortawesome/angular-fontawesome": "^0.14.1", "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", @@ -2762,6 +2763,23 @@ "node": ">=10.0.0" } }, + "node_modules/@ecoacoustics/web-components": { + "version": "0.1.1-alpha.4", + "resolved": "https://registry.npmjs.org/@ecoacoustics/web-components/-/web-components-0.1.1-alpha.4.tgz", + "integrity": "sha512-wPaJC26BNKMduCBIjep8U6keAX8wkI8Y4K98MR4m4wMXjhUaWpq30ret4R50oIxPPQqZ5MRDZYOaukvspF1rSw==", + "dependencies": { + "@json2csv/plainjs": "^7.0.6", + "@lit-labs/preact-signals": "^1.0.2", + "@lit/context": "^1.1.1", + "change-case": "^5.4.4", + "chroma-js": "^2.4.2", + "csvtojson": "^2.0.10", + "fft-windowing-ts": "^0.2.7", + "lit": "^3.1.3", + "lucide-static": "^0.363.0", + "music-metadata-browser": "^2.5.10" + } + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.41.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz", @@ -3594,12 +3612,56 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@json2csv/formatters": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@json2csv/formatters/-/formatters-7.0.6.tgz", + "integrity": "sha512-hjIk1H1TR4ydU5ntIENEPgoMGW+Q7mJ+537sDFDbsk+Y3EPl2i4NfFVjw0NJRgT+ihm8X30M67mA8AS6jPidSA==" + }, + "node_modules/@json2csv/plainjs": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@json2csv/plainjs/-/plainjs-7.0.6.tgz", + "integrity": "sha512-4Md7RPDCSYpmW1HWIpWBOqCd4vWfIqm53S3e/uzQ62iGi7L3r34fK/8nhOMEe+/eVfCx8+gdSCt1d74SlacQHw==", + "dependencies": { + "@json2csv/formatters": "^7.0.6", + "@streamparser/json": "^0.0.20" + } + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@lit-labs/preact-signals": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lit-labs/preact-signals/-/preact-signals-1.0.2.tgz", + "integrity": "sha512-HFgIhqLB5IiNbvJxEN3+o6n9x/fNZo7pqfElG56NHrOFBsIFW7wswbp6hHeoGzATQDOB2ZmrH/VrRGYdcgl29g==", + "dependencies": { + "@preact/signals-core": "^1.3.0", + "lit": "^3.1.2" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", + "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==" + }, + "node_modules/@lit/context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.2.tgz", + "integrity": "sha512-S0nw2C6Tkm7fVX5TGYqeROGD+Z9Coa2iFpW+ysYBDH3YvCqOY3wVQvSgwbaliLJkjTnSEYCBe9qFqKV8WUFpVw==", + "dependencies": { + "@lit/reactive-element": "^1.6.2 || ^2.0.0" + } + }, + "node_modules/@lit/reactive-element": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", + "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0" + } + }, "node_modules/@ljharb/through": { "version": "2.3.12", "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.12.tgz", @@ -4239,6 +4301,15 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@preact/signals-core": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.6.1.tgz", + "integrity": "sha512-KXEEmJoKDlo0Igju/cj9YvKIgyaWFDgnprShQjzimUd5VynAAdTWMshawEOjUVeKbsI0aR58V6WOQp+DNcKApw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", @@ -4513,6 +4584,11 @@ "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", "dev": true }, + "node_modules/@streamparser/json": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.20.tgz", + "integrity": "sha512-VqAAkydywPpkw63WQhPVKCD3SdwXuihCUVZbbiY3SfSTGQyHmwRoq27y4dmJdZuJwd5JIlQoMPyGvMbUPY0RKQ==" + }, "node_modules/@swimlane/ngx-datatable": { "version": "20.1.0", "resolved": "https://registry.npmjs.org/@swimlane/ngx-datatable/-/ngx-datatable-20.1.0.tgz", @@ -4625,6 +4701,11 @@ "node": ">=8" } }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -5873,6 +5954,17 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -6634,6 +6726,11 @@ "node": ">=6.9.x" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -7245,6 +7342,11 @@ "node": ">=4" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==" + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -7286,6 +7388,11 @@ "node": ">=10" } }, + "node_modules/chroma-js": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", + "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -8086,6 +8193,33 @@ "node": ">=4" } }, + "node_modules/csvtojson": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz", + "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==", + "dependencies": { + "bluebird": "^3.5.1", + "lodash": "^4.17.3", + "strip-bom": "^2.0.0" + }, + "bin": { + "csvtojson": "bin/csvtojson" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/csvtojson/node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -9956,6 +10090,14 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -9966,7 +10108,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "engines": { "node": ">=0.8.x" } @@ -10246,6 +10387,11 @@ "pend": "~1.2.0" } }, + "node_modules/fft-windowing-ts": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/fft-windowing-ts/-/fft-windowing-ts-0.2.7.tgz", + "integrity": "sha512-xJOVJPn6JhNBq5Tgd2NYghbzRQOh1ub/zUFldPCyNujScqia5jqX+GLVhzXVD6imAyK4KaMjG+iBeHycE2JV9w==" + }, "node_modules/figures": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", @@ -10286,6 +10432,22 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -11904,6 +12066,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" + }, "node_modules/is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -13326,6 +13493,34 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/lit": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.4.tgz", + "integrity": "sha512-q6qKnKXHy2g1kjBaNfcoLlgbI3+aSOZ9Q4tiGa9bGYXq5RBXxkVTqTIVmP2VWMp29L4GyvCFm8ZQ2o56eUAMyA==", + "dependencies": { + "@lit/reactive-element": "^2.0.4", + "lit-element": "^4.0.4", + "lit-html": "^3.1.2" + } + }, + "node_modules/lit-element": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.6.tgz", + "integrity": "sha512-U4sdJ3CSQip7sLGZ/uJskO5hGiqtlpxndsLr6mt3IQIjheg93UKYeGQjWMRql1s/cXNOaRrCzC2FQwjIwSUqkg==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.0.4", + "lit-html": "^3.1.2" + } + }, + "node_modules/lit-element/node_modules/lit-html": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.4.tgz", + "integrity": "sha512-yKKO2uVv7zYFHlWMfZmqc+4hkmSbFp8jgjdZY9vvR9jr4J8fH6FUMXhr+ljfELgmjpvlF7Z1SJ5n5/Jeqtc9YA==", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/lit-html": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", @@ -13334,6 +13529,14 @@ "@types/trusted-types": "^2.0.2" } }, + "node_modules/lit/node_modules/lit-html": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.4.tgz", + "integrity": "sha512-yKKO2uVv7zYFHlWMfZmqc+4hkmSbFp8jgjdZY9vvR9jr4J8fH6FUMXhr+ljfELgmjpvlF7Z1SJ5n5/Jeqtc9YA==", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -13367,8 +13570,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -13424,6 +13626,11 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-static": { + "version": "0.363.0", + "resolved": "https://registry.npmjs.org/lucide-static/-/lucide-static-0.363.0.tgz", + "integrity": "sha512-M3qAYZaXC9UIP+i2+bB5f7stYzyCBRPkXqxm8EQF6Ys2MXXbdHzkReHDdstoa8VKJznoXm/Br1bDf67BoVnSBQ==" + }, "node_modules/luxon": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", @@ -13888,6 +14095,66 @@ "multicast-dns": "cli.js" } }, + "node_modules/music-metadata": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-7.14.0.tgz", + "integrity": "sha512-xrm3w7SV0Wk+OythZcSbaI8mcr/KHd0knJieu8bVpaPfMv/Agz5EooCAPz3OR5hbYMiUG6dgAPKZKnMzV+3amA==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "content-type": "^1.0.5", + "debug": "^4.3.4", + "file-type": "^16.5.4", + "media-typer": "^1.1.0", + "strtok3": "^6.3.0", + "token-types": "^4.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/music-metadata-browser": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/music-metadata-browser/-/music-metadata-browser-2.5.10.tgz", + "integrity": "sha512-03UnAmsSJoZZ5kK2BnEnd2zpH8LXRWQ6xlc7akKudhc2d9FT+yAiqapnmOzjW3g4cxxvIsSK5MVBO2Gi+Ymjfw==", + "dependencies": { + "buffer": "^6.0.3", + "debug": "^4.3.4", + "music-metadata": "^7.13.3", + "readable-stream": "^4.3.0", + "readable-web-to-node-stream": "^3.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/music-metadata-browser/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/music-metadata/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/mute-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", @@ -15328,6 +15595,18 @@ "node": ">=8" } }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -15706,6 +15985,14 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -16113,7 +16400,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -16123,6 +16409,21 @@ "node": ">= 6" } }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "dependencies": { + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -17847,7 +18148,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -17996,6 +18296,22 @@ "node": ">=4" } }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -18303,6 +18619,22 @@ "node": ">=0.6" } }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/topojson-client": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", @@ -18915,8 +19247,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utils-merge": { "version": "1.0.1", diff --git a/package.json b/package.json index fe04e6c91..989965df8 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@angular/platform-server": "17.1.2", "@angular/router": "17.1.2", "@angular/ssr": "^17.1.2", + "@ecoacoustics/web-components": "^0.1.1-alpha.4", "@fortawesome/angular-fontawesome": "^0.14.1", "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ad1035a6b..e8da634ce 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,4 +1,4 @@ -import { DoBootstrap, NgModule } from "@angular/core"; +import { CUSTOM_ELEMENTS_SCHEMA, DoBootstrap, NgModule } from "@angular/core"; import { ReactiveFormsModule } from "@angular/forms"; import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; @@ -24,6 +24,7 @@ import { environment } from "src/environments/environment"; import { TitleStrategy } from "@angular/router"; import { AnnotationsImportModule } from "@components/import-annotations/import-annotations.module"; import { WebsiteStatusModule } from "@components/website-status/website-status.module"; +import { VerificationModule } from "@components/verification/verification.module"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent, PageTitleStrategy } from "./app.component"; import { toastrRoot } from "./app.helper"; @@ -64,6 +65,7 @@ export const appImports = [ DataRequestModule, HarvestModule, ReportsModule, + VerificationModule, AnnotationsImportModule, LibraryModule, ListenModule, @@ -105,6 +107,7 @@ export const appImports = [ { provide: LOADING_BAR_CONFIG, useValue: { latencyThreshold: 200 } }, ], exports: [], + schemas: [CUSTOM_ELEMENTS_SCHEMA], }) export class AppModule implements DoBootstrap { public ngDoBootstrap(app: any): void { diff --git a/src/app/components/projects/pages/details/details.component.ts b/src/app/components/projects/pages/details/details.component.ts index 02446386b..8928aa599 100644 --- a/src/app/components/projects/pages/details/details.component.ts +++ b/src/app/components/projects/pages/details/details.component.ts @@ -36,6 +36,7 @@ import { NgbPaginationConfig } from "@ng-bootstrap/ng-bootstrap"; import { List } from "immutable"; import { ToastrService } from "ngx-toastr"; import { merge, Observable, takeUntil } from "rxjs"; +import { verificationMenuItems } from "@components/verification/verification.menu"; export const projectMenuItemActions = [ visualizeMenuItem, @@ -49,6 +50,7 @@ export const projectMenuItemActions = [ audioRecordingMenuItems.batch.project, harvestsMenuItem, reportMenuItems.new.project, + verificationMenuItems.new.project, ]; const projectKey = "project"; diff --git a/src/app/components/regions/pages/details/details.component.ts b/src/app/components/regions/pages/details/details.component.ts index 6b5257086..ca56521f5 100644 --- a/src/app/components/regions/pages/details/details.component.ts +++ b/src/app/components/regions/pages/details/details.component.ts @@ -30,6 +30,7 @@ import { ConfigService } from "@services/config/config.service"; import { List } from "immutable"; import { ToastrService } from "ngx-toastr"; import { takeUntil } from "rxjs"; +import { verificationMenuItems } from "@components/verification/verification.menu"; export const regionMenuItemActions = [ deleteRegionModal, @@ -39,6 +40,7 @@ export const regionMenuItemActions = [ audioRecordingMenuItems.list.region, audioRecordingMenuItems.batch.region, reportMenuItems.new.region, + verificationMenuItems.new.region, ]; const projectKey = "project"; diff --git a/src/app/components/sites/pages/details/details.component.ts b/src/app/components/sites/pages/details/details.component.ts index 05a4acb84..3b76dcfa1 100644 --- a/src/app/components/sites/pages/details/details.component.ts +++ b/src/app/components/sites/pages/details/details.component.ts @@ -29,6 +29,7 @@ import { takeUntil } from "rxjs"; import { ConfigService } from "@services/config/config.service"; import { shallowRegionsRoute } from "@components/regions/regions.routes"; import { reportMenuItems } from "@components/reports/reports.menu"; +import { verificationMenuItems } from "@components/verification/verification.menu"; import { editSiteMenuItem, siteMenuItem, @@ -43,6 +44,7 @@ export const siteMenuItemActions = [ audioRecordingMenuItems.list.site, audioRecordingMenuItems.batch.site, reportMenuItems.new.site, + verificationMenuItems.new.site, ]; export const pointMenuItemActions = [ @@ -53,6 +55,7 @@ export const pointMenuItemActions = [ audioRecordingMenuItems.list.siteAndRegion, audioRecordingMenuItems.batch.siteAndRegion, reportMenuItems.new.siteAndRegion, + verificationMenuItems.new.siteAndRegion, ]; const projectKey = "project"; diff --git a/src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.html b/src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.html new file mode 100644 index 000000000..a6117b3b4 --- /dev/null +++ b/src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.html @@ -0,0 +1,19 @@ +

+ No items found +

+ +
+ @if((audioEvents | async).length > 0) { + @for (audioEvent of audioEvents | async; track $index) { + + } + } + @else { +

No audio events found

+ } +
diff --git a/src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.scss b/src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.scss new file mode 100644 index 000000000..d1ad006b9 --- /dev/null +++ b/src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.scss @@ -0,0 +1,4 @@ +.preview-container { + position: relative; + width: 100%; +} diff --git a/src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.spec.ts b/src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.ts b/src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.ts new file mode 100644 index 000000000..3f6afe92f --- /dev/null +++ b/src/app/components/verification/components/annotation-search-preview/annotation-search-preview.component.ts @@ -0,0 +1,85 @@ +import { Component, Input, OnChanges } from "@angular/core"; +import { ShallowAudioEventsService } from "@baw-api/audio-event/audio-events.service"; +import { Filters } from "@baw-api/baw-api.service"; +import { AudioEvent } from "@models/AudioEvent"; +import { Project } from "@models/Project"; +import { Region } from "@models/Region"; +import { Site } from "@models/Site"; +import { Tag } from "@models/Tag"; +import { DateTimeFilterModel } from "@shared/date-time-filter/date-time-filter.component"; +import { Observable, of } from "rxjs"; + +@Component({ + selector: "baw-annotation-search-preview", + templateUrl: "annotation-search-preview.component.html", + styleUrl: "annotation-search-preview.component.scss", +}) +export class AnnotationSearchPreviewComponent implements OnChanges { + public constructor(private audioEventsApi: ShallowAudioEventsService) {} + + @Input({ required: true }) + public project: Project; + + @Input({ required: true }) + public regions: Region[]; + + @Input({ required: true }) + public sites: Site[]; + + @Input({ required: true }) + public tags: Tag[]; + + @Input({ required: true }) + public dateFilters: DateTimeFilterModel; + + @Input({ required: true }) + public onlyUnverified: boolean; + + protected audioEvents: Observable = of(); + + public ngOnChanges(): void { + const filters = this.buildFilter( + this.project, + this.regions, + this.sites, + this.tags, + this.dateFilters + ); + + this.audioEvents = this.audioEventsApi.filter(filters) + } + + protected buildAudioUrl(audioEvent: AudioEvent): string { + const basePath = `https://api.staging.ecosounds.org/audio_recordings/${audioEvent.audioRecordingId}/original`; + const urlParams = `?end_offset=${audioEvent.endTimeSeconds}&start_offset=${audioEvent.startTimeSeconds}`; + return basePath + urlParams; + } + + private buildFilter( + project: Project, + regions: Region[], + sites: Site[], + tags: Tag[], + dateFilters: DateTimeFilterModel + ): Filters { + return { + filter: { + isReference: { + eq: true, + }, + "tags.id": { + in: tags.map((tag) => tag.id), + }, + "projects.id": { + eq: project.id, + }, + "regions.id": { + in: regions.map((region) => region.id), + }, + "sites.id": { + in: sites.map((site) => site.id), + }, + } + } as Filters; + } +} diff --git a/src/app/components/verification/pages/new/new.component.html b/src/app/components/verification/pages/new/new.component.html new file mode 100644 index 000000000..2e2688ecd --- /dev/null +++ b/src/app/components/verification/pages/new/new.component.html @@ -0,0 +1,55 @@ +

{{ pageTitle }}

+

Annotation Search

+ + + + + + + + + + +
+ + +
+ +
+ + +@if (model.project) { + +} diff --git a/src/app/components/verification/pages/new/new.component.scss b/src/app/components/verification/pages/new/new.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/components/verification/pages/new/new.component.spec.ts b/src/app/components/verification/pages/new/new.component.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/components/verification/pages/new/new.component.ts b/src/app/components/verification/pages/new/new.component.ts new file mode 100644 index 000000000..9141bc751 --- /dev/null +++ b/src/app/components/verification/pages/new/new.component.ts @@ -0,0 +1,171 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { StandardApi } from "@baw-api/api-common"; +import { InnerFilter } from "@baw-api/baw-api.service"; +import { projectResolvers } from "@baw-api/project/projects.service"; +import { + regionResolvers, + ShallowRegionsService, +} from "@baw-api/region/regions.service"; +import { retrieveResolvers } from "@baw-api/resolver-common"; +import { + ShallowSitesService, + siteResolvers, +} from "@baw-api/site/sites.service"; +import { TagsService } from "@baw-api/tag/tags.service"; +import { siteAnnotationsModal } from "@components/sites/sites.modals"; +import { verificationMenuItems } from "@components/verification/verification.menu"; +import { + contains, + filterAnd, + filterModel, + notIn, +} from "@helpers/filters/filters"; +import { PageComponent } from "@helpers/page/pageComponent"; +import { IPageInfo } from "@helpers/page/pageInfo"; +import { AbstractModel } from "@models/AbstractModel"; +import { Project } from "@models/Project"; +import { Region } from "@models/Region"; +import { Site } from "@models/Site"; +import { Tag } from "@models/Tag"; +import { DateTimeFilterModel } from "@shared/date-time-filter/date-time-filter.component"; +import { TypeaheadSearchCallback } from "@shared/typeahead-input/typeahead-input.component"; +import { List } from "immutable"; +import { Observable } from "rxjs"; + +const projectKey = "project"; +const regionKey = "region"; +const siteKey = "site"; + +interface VerificationSearch { + tags?: Tag[]; + project?: Project; + regions?: Region[]; + sites?: Site[]; + dateFilters?: DateTimeFilterModel; + onlyUnverified?: boolean; +} + +@Component({ + selector: "baw-new-verification", + templateUrl: "new.component.html", + styleUrl: "new.component.scss", +}) +class NewVerificationComponent extends PageComponent implements OnInit { + public constructor( + protected sitesApi: ShallowSitesService, + protected regionsApi: ShallowRegionsService, + protected tagsApi: TagsService, + private route: ActivatedRoute + ) { + super(); + } + + protected project: Project; + protected region?: Region; + protected site?: Site; + + protected model: VerificationSearch = { + regions: [], + sites: [], + tags: [], + dateFilters: {}, + onlyUnverified: false, + }; + + protected get pageTitle(): string { + if (this.site) { + return this.site.isPoint + ? `Point: ${this.site.name}` + : `Site: ${this.site.name}`; + } else if (this.region) { + return `Site: ${this.region.name}`; + } + + return `Project: ${this.project.name}`; + } + + public ngOnInit(): void { + const models = retrieveResolvers(this.route.snapshot.data as IPageInfo); + this.project = models[projectKey] as Project; + + // generating a report from the region, or site level will immutably scope the report to the model(s) + if (models[regionKey]) { + this.region = models[regionKey] as Region; + this.model.regions = [this.region]; + } + + if (models[siteKey]) { + this.site = models[siteKey] as Site; + this.model.sites = [this.site]; + } + } + + protected createSearchCallback( + api: StandardApi, + key: string = "name", + includeDefaultFilters: boolean = true + ): TypeaheadSearchCallback { + return (text: string, activeItems: T[]): Observable => + api.filter({ + filter: filterAnd( + contains( + key as keyof T, + text as any, + includeDefaultFilters && this.defaultFilter() + ), + notIn(key as keyof AbstractModel, activeItems) + ), + }); + } + + // we need a default filter to scope to projects, regions, sites + private defaultFilter(): InnerFilter { + // we don't need to filter for every route, we only need to filter for the lowest level + // this is because all sites have a region, all regions have a project, etc.. + // so it can be logically inferred + if (this.site) { + return filterModel("sites", this.site); + } else if (this.region) { + return filterModel("regions", this.region); + } else { + return filterModel("projects", this.project); + } + } + + protected convertToTag(model: object[]): Tag[] { + return model as Tag[]; + } + + protected convertToRegion(model: object[]): Region[] { + return model as Region[]; + } + + protected convertToSite(model: object[]): Site[] { + return model as Site[]; + } +} + +function getPageInfo( + subRoute: keyof typeof verificationMenuItems.new +): IPageInfo { + return { + pageRoute: verificationMenuItems.new[subRoute], + category: verificationMenuItems.new[subRoute], + menus: { + actions: List([verificationMenuItems.view[subRoute], siteAnnotationsModal]), + }, + resolvers: { + [projectKey]: projectResolvers.showOptional, + [regionKey]: regionResolvers.showOptional, + [siteKey]: siteResolvers.showOptional, + }, + }; +} + +NewVerificationComponent.linkToRoute(getPageInfo("project")) + .linkToRoute(getPageInfo("region")) + .linkToRoute(getPageInfo("site")) + .linkToRoute(getPageInfo("siteAndRegion")); + +export { NewVerificationComponent }; diff --git a/src/app/components/verification/pages/view/view.component.html b/src/app/components/verification/pages/view/view.component.html new file mode 100644 index 000000000..0643ca9ee --- /dev/null +++ b/src/app/components/verification/pages/view/view.component.html @@ -0,0 +1,32 @@ + + +
+ + + + Koala + Koala + Cow + Negative + Skip + + + + + + +
diff --git a/src/app/components/verification/pages/view/view.component.scss b/src/app/components/verification/pages/view/view.component.scss new file mode 100644 index 000000000..fa8a50af5 --- /dev/null +++ b/src/app/components/verification/pages/view/view.component.scss @@ -0,0 +1,3 @@ +.collapsed-search { + padding: 1rem; +} diff --git a/src/app/components/verification/pages/view/view.component.spec.ts b/src/app/components/verification/pages/view/view.component.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/components/verification/pages/view/view.component.ts b/src/app/components/verification/pages/view/view.component.ts new file mode 100644 index 000000000..0acfce343 --- /dev/null +++ b/src/app/components/verification/pages/view/view.component.ts @@ -0,0 +1,38 @@ +import { Component } from "@angular/core"; +import { projectResolvers } from "@baw-api/project/projects.service"; +import { regionResolvers } from "@baw-api/region/regions.service"; +import { siteResolvers } from "@baw-api/site/sites.service"; +import { verificationMenuItems } from "@components/verification/verification.menu"; +import { PageComponent } from "@helpers/page/pageComponent"; +import { IPageInfo } from "@helpers/page/pageInfo"; + +const projectKey = "project"; +const regionKey = "region"; +const siteKey = "site"; + +@Component({ + selector: "baw-verification", + templateUrl: "view.component.html", + styleUrl: "view.component.scss", +}) +class ViewVerificationComponent extends PageComponent { } + +function getPageInfo(subRoute: keyof typeof verificationMenuItems.view): IPageInfo { + return { + pageRoute: verificationMenuItems.view[subRoute], + category: verificationMenuItems.view[subRoute], + resolvers: { + [projectKey]: projectResolvers.showOptional, + [regionKey]: regionResolvers.showOptional, + [siteKey]: siteResolvers.showOptional, + }, + }; +} + +ViewVerificationComponent.linkToRoute(getPageInfo("project")) + .linkToRoute(getPageInfo("region")) + .linkToRoute(getPageInfo("site")) + .linkToRoute(getPageInfo("siteAndRegion")); + +export { ViewVerificationComponent }; + diff --git a/src/app/components/verification/verification.menu.ts b/src/app/components/verification/verification.menu.ts new file mode 100644 index 000000000..a5928b14b --- /dev/null +++ b/src/app/components/verification/verification.menu.ts @@ -0,0 +1,96 @@ +import { Category, menuRoute, MenuRoute } from "@interfaces/menusInterfaces"; +import { siteMenuItem } from "@components/sites/sites.menus"; +import { pointMenuItem } from "@components/sites/points.menus"; +import { regionMenuItem } from "@components/regions/regions.menus"; +import { projectMenuItem } from "@components/projects/projects.menus"; +import { newVerificationRoute, verificationRoute, VerificationRoute } from "./verification.routes"; + +export type VerificationMenuRoutes = Record; + +function makeViewVerificationCategory(subRoute: VerificationRoute): Category { + return { + icon: ["fas", "circle-check"], + label: "Verification", + route: verificationRoute[subRoute], + }; +} + +function makeNewVerificationCategory(subRoute: VerificationRoute): Category { + return { + icon: ["fas", "circle-check"], + label: "New Verification", + route: newVerificationRoute[subRoute], + }; +} + +function makeViewVerificationMenuItem( + subRoute: VerificationRoute, + parent?: MenuRoute, +) { + return menuRoute({ + icon: ["fas", "circle-check"], + label: "Verify this list", + tooltip: () => "Verify audio events", + route: verificationRoute[subRoute], + parent, + }); +} + +function makeNewVerificationMenuItem( + subRoute: VerificationRoute, + parent?: MenuRoute, +) { + return menuRoute({ + icon: ["fas", "circle-check"], + label: "New Verification", + tooltip: () => "New verification", + route: newVerificationRoute[subRoute], + parent, + }); +} + +const viewVerificationMenuItem: VerificationMenuRoutes = { + /** /project/:projectId/site/:siteId/verification */ + site: makeViewVerificationMenuItem("site", siteMenuItem), + /** /project/:projectId/region/:regionId/site/:siteId/verification */ + siteAndRegion: makeViewVerificationMenuItem("siteAndRegion", pointMenuItem), + /** /project/:projectId/region/:regionId/verification */ + region: makeViewVerificationMenuItem("region", regionMenuItem), + /** /project/:projectId/verification */ + project: makeViewVerificationMenuItem("project", projectMenuItem), +}; + +const newVerificationMenuitem: VerificationMenuRoutes = { + /** /project/:projectId/site/:siteId/verification */ + site: makeNewVerificationMenuItem("site", siteMenuItem), + /** /project/:projectId/region/:regionId/site/:siteId/verification */ + siteAndRegion: makeNewVerificationMenuItem("siteAndRegion", pointMenuItem), + /** /project/:projectId/region/:regionId/verification */ + region: makeNewVerificationMenuItem("region", regionMenuItem), + /** /project/:projectId/verification */ + project: makeNewVerificationMenuItem("project", projectMenuItem), +}; + +const viewVerificationCategory = { + site: makeViewVerificationCategory("site"), + siteAndRegion: makeViewVerificationCategory("siteAndRegion"), + region: makeViewVerificationCategory("region"), + project: makeViewVerificationCategory("project"), +}; + +const newVerificationCategory = { + site: makeNewVerificationCategory("site"), + siteAndRegion: makeNewVerificationCategory("siteAndRegion"), + region: makeNewVerificationCategory("region"), + project: makeNewVerificationCategory("project"), +}; + +export const verificationCategories = { + new: newVerificationCategory, + view: viewVerificationCategory, +}; + +export const verificationMenuItems = { + new: newVerificationMenuitem, + view: viewVerificationMenuItem, +}; diff --git a/src/app/components/verification/verification.module.ts b/src/app/components/verification/verification.module.ts new file mode 100644 index 000000000..f6950f6ea --- /dev/null +++ b/src/app/components/verification/verification.module.ts @@ -0,0 +1,30 @@ +import { getRouteConfigForPage } from "@helpers/page/pageRouting"; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; +import { SharedModule } from "@shared/shared.module"; +import { RouterModule } from "@angular/router"; +import { StrongRoute } from "@interfaces/strongRoute"; +import { verificationRoute } from "./verification.routes"; +import { NewVerificationComponent } from "./pages/new/new.component"; +import { ViewVerificationComponent } from "./pages/view/view.component"; +import { AnnotationSearchPreviewComponent } from "./components/annotation-search-preview/annotation-search-preview.component"; + +const internalComponents = [ + AnnotationSearchPreviewComponent, +]; + +const components = [ + NewVerificationComponent, + ViewVerificationComponent, +]; + +const routes = Object.values(verificationRoute) + .map((route: StrongRoute) => route.compileRoutes(getRouteConfigForPage)) + .flat(); + +@NgModule({ + declarations: [...internalComponents, ...components], + imports: [SharedModule, RouterModule.forChild(routes)], + exports: [RouterModule, ...components], + schemas: [CUSTOM_ELEMENTS_SCHEMA], +}) +export class VerificationModule {} diff --git a/src/app/components/verification/verification.routes.ts b/src/app/components/verification/verification.routes.ts new file mode 100644 index 000000000..701c3ad76 --- /dev/null +++ b/src/app/components/verification/verification.routes.ts @@ -0,0 +1,37 @@ +import { projectRoute } from "@components/projects/projects.routes"; +import { regionRoute } from "@components/regions/regions.routes"; +import { pointRoute } from "@components/sites/points.routes"; +import { siteRoute } from "@components/sites/sites.routes"; +import { StrongRoute } from "@interfaces/strongRoute"; + +const verificationRouteName = "verification"; + +const verificationRouteQueryParamResolver = (params) => + params + ? {} + : {}; + +export type VerificationRoute = "project" | "region" | "site" | "siteAndRegion"; +export type VerificationStrongRoute = Record; + +export const verificationRoute: VerificationStrongRoute = { + /** /project/:projectId/site/:siteId/verification */ + site: siteRoute.add(verificationRouteName, verificationRouteQueryParamResolver), + /** /project/:projectId/region/:regionId/site/:siteId/verification */ + siteAndRegion: pointRoute.add(verificationRouteName, verificationRouteQueryParamResolver), + /** /project/:projectId/region/:regionId/verification */ + region: regionRoute.add(verificationRouteName, verificationRouteQueryParamResolver), + /** /project/:projectId/verification */ + project: projectRoute.add(verificationRouteName, verificationRouteQueryParamResolver), +}; + +export const newVerificationRoute: VerificationStrongRoute = { + /** /project/:projectId/site/:siteId/verification/new */ + site: verificationRoute.site.add("new"), + /** /project/:projectId/region/:regionId/site/:siteId/verification/new */ + siteAndRegion: verificationRoute.siteAndRegion.add("new"), + /** /project/:projectId/region/:regionId/verification/new */ + region: verificationRoute.region.add("new"), + /** /project/:projectId/verification/new */ + project: verificationRoute.project.add("new"), +}; diff --git a/src/app/services/theme/theme.service.ts b/src/app/services/theme/theme.service.ts index 0cc9909c5..5833985e5 100644 --- a/src/app/services/theme/theme.service.ts +++ b/src/app/services/theme/theme.service.ts @@ -102,11 +102,19 @@ export class ThemeService { public setColor(colorName: ThemeColor, color?: HSLColor | string): void { if (!this.isServer) { this.setColorInBrowser(colorName, color); + + if (colorName === "highlight") { + this.setOeColorsInBrowser(color as HSLColor); + } return; } // Can only set color on SSR if it exists if (color) { + if (colorName === "highlight") { + this.setOeColorsInSsr(color as HSLColor); + } + this.setColorInSsr(colorName, color); } } @@ -217,6 +225,47 @@ export class ThemeService { this.document.head.appendChild(style); } + // oe colours are used in third party web components such as the oe-verification-grid + // because they expose theming options via css variables, we want to set these colours + // to the same as our theme colours so that the components match the rest of the site + private setOeColorsInBrowser(highlightColor: HSLColor | string): void { + const hslColor = typeof highlightColor === "string" ? hsl(highlightColor) : highlightColor; + + const h = this.readHslValue(hslColor.h); + const s = this.readHslValue(hslColor.s, true); + const l = this.readHslValue(hslColor.l, true); + + const style = this.document.createElement("style"); + + style.innerHTML = ` + :root { + --oe-theme-hue: ${h}deg !important; + --oe-theme-saturation: ${s}% !important; + --oe-theme-lightness: ${l}% !important; + } + `; + + this.document.head.appendChild(style); + } + + private setOeColorsInSsr(highlightColor: HSLColor | string): void { + const hslColor = typeof highlightColor === "string" ? hsl(highlightColor) : highlightColor; + + const style = this.document.createElement("style"); + const h = this.readHslValue(hslColor.h); + const s = this.readHslValue(hslColor.s, true); + const l = this.readHslValue(hslColor.l, true); + + style.innerHTML = ` + :root { + --oe-theme-hue: ${h}deg !important; + --oe-theme-saturation: ${s}% !important; + --oe-theme-lightness: ${l}% !important; + } + `; + this.document.head.appendChild(style); + } + private getCssVariables(colorName: ThemeColor) { const prefix = `--baw-${colorName}`; return { diff --git a/src/index.html b/src/index.html index 754495596..bf810246c 100644 --- a/src/index.html +++ b/src/index.html @@ -16,6 +16,9 @@ + + +