diff --git a/.eslintignore b/.eslintignore index 6de2f63d7e98adaa0c32f09013e16a7d57485681..b699cf19c45c19bee0ac8091aa741651b2589b85 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,3 @@ -*.spec.ts \ No newline at end of file +*.spec.ts +src/atlasComponents/sapi/schema.ts +**/*.stories.ts \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index e01d3ce69821ff914b61facb9543909eab401ae9..5001d24d9b76175066ac3d5185d51ace55414a16 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,33 +1,27 @@ module.exports = { root: true, parser: '@typescript-eslint/parser', - plugins: [ - '@typescript-eslint', - ], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', - ], + plugins: ['@typescript-eslint'], + extends: ["eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "plugin:storybook/recommended"], rules: { "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/interface-name-prefix":[0], + "@typescript-eslint/interface-name-prefix": [0], // "no-unused-vars": "off", "semi": "off", - "indent":["error", 2, { - "FunctionDeclaration":{ - "body":1, - "parameters":2 + "indent": ["error", 2, { + "FunctionDeclaration": { + "body": 1, + "parameters": 2 } }], "@typescript-eslint/member-delimiter-style": [2, { "multiline": { - "delimiter": "none", - "requireLast": true + "delimiter": "none", + "requireLast": true }, "singleline": { - "delimiter": "comma", - "requireLast": false + "delimiter": "comma", + "requireLast": false } }], "@typescript-eslint/no-unused-vars": ["warn", { diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..8acbc9780e8547d6bec7148ddbfcd05a02797ca8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +src/atlasComponents/sapi/schema.ts linguist-generated=true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3c07d114df83ae2c2dc663da720cc5451914f31..78d9a7e7b59248e5b280835a47c7860b579061e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Use Node.js 14.x for lint + - name: Use Node.js 16.x for lint uses: actions/setup-node@v1 with: - node-version: '14.x' + node-version: '16.x' - run: npm i - run: npm run lint @@ -28,7 +28,7 @@ jobs: strategy: matrix: - node-version: [12.x, 14.x, 16.x] + node-version: [16.x] env: NODE_ENV: test @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x, 14.x, 16.x] + node-version: [16.x] env: NODE_ENV: test diff --git a/.github/workflows/docker_img.yml b/.github/workflows/docker_img.yml index 495f946a603cb7cd81a61d4416fda31b560a64fb..47734fc27ba6540ee111646142553af53a365798 100644 --- a/.github/workflows/docker_img.yml +++ b/.github/workflows/docker_img.yml @@ -18,9 +18,9 @@ jobs: PRODUCTION: 'true' DOCKER_REGISTRY: 'docker-registry.ebrains.eu/siibra/' - SIIBRA_API_STABLE: 'https://siibra-api-stable.apps.hbp.eu/v1_0' - SIIBRA_API_RC: 'https://siibra-api-rc.apps.hbp.eu/v1_0' - SIIBRA_API_LATEST: 'https://siibra-api-latest.apps-dev.hbp.eu/v1_0' + SIIBRA_API_STABLE: 'https://siibra-api-stable.apps.hbp.eu/v2_0' + SIIBRA_API_RC: 'https://siibra-api-rc.apps.hbp.eu/v2_0' + SIIBRA_API_LATEST: 'https://siibra-api-latest.apps-dev.hbp.eu/v2_0' steps: @@ -46,7 +46,7 @@ jobs: if [[ "$GITHUB_REF" == *hotfix* ]] then echo "Hotfix branch, using prod env..." - echo "BS_REST_URL=${{ env.SIIBRA_API_STABLE }}" >> $GITHUB_ENV + echo "BS_REST_URL=${{ env.SIIBRA_API_RC }}" >> $GITHUB_ENV else echo "Using dev env..." echo "BS_REST_URL=${{ env.SIIBRA_API_LATEST }}" >> $GITHUB_ENV diff --git a/.gitignore b/.gitignore index 05ba580503a02c6b0557b29836fa8bc2a42b4295..fadb807b38bbbade09582f298c5ace3d94b0c031 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/.angular/cache node_modules dist src/res/raw @@ -12,3 +13,5 @@ site *.log cachedKgDataset.json tmp +storybook-static +documentation.json diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 0000000000000000000000000000000000000000..e420138441efbb7ec2a1d085d1f62e2a0ce847d2 --- /dev/null +++ b/.storybook/main.js @@ -0,0 +1,19 @@ +module.exports = { + "stories": [ + "../src/**/*.stories.mdx", + "../src/**/*.stories.@(js|jsx|ts|tsx)" + ], + "addons": [ + "storybook-dark-mode", + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-interactions" + ], + "framework": "@storybook/angular", + "core": { + "builder": "webpack5" + }, + features: { + interactionsDebugger: true, + }, +} diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 0000000000000000000000000000000000000000..392e96571a4090735d8407bf9a93592d0344d34b --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,13 @@ +<script> + console.log("./storybook/preview-head.html working properly") +</script> +<script src="https://unpkg.com/kg-dataset-previewer@1.2.0/dist/kg-dataset-previewer/kg-dataset-previewer.js" defer></script> +<script src="main.bundle.js" defer></script> +<style> + body + { + overflow: scroll!important; + } +</style> +<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous"> +<script type="module" src="https://unpkg.com/hbp-connectivity-component@0.6.2/dist/connectivity-component/connectivity-component.js" defer></script> diff --git a/.storybook/preview.js b/.storybook/preview.js new file mode 100644 index 0000000000000000000000000000000000000000..957e7cbef107fa2c3191e57245241fb901983f17 --- /dev/null +++ b/.storybook/preview.js @@ -0,0 +1,20 @@ +import { setCompodocJson } from "@storybook/addon-docs/angular"; +import docJson from "../documentation.json"; +setCompodocJson(docJson); + +import 'src/theme.scss' + +export const parameters = { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + docs: { inlineStories: true }, + darkMode: { + // Set the initial theme + current: 'light' + } +} diff --git a/.storybook/tsconfig.json b/.storybook/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..7aea16334431b3c5f37c10e2c9c71bc224562c21 --- /dev/null +++ b/.storybook/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../tsconfig.app.json", + "compilerOptions": { + "types": [ + "node" + ], + "allowSyntheticDefaultImports": true + }, + "exclude": [ + "../src/test.ts", + "../src/**/*.spec.ts", + "../projects/**/*.spec.ts" + ], + "include": [ + "../src/**/*", + "../projects/**/*" + ], + "files": [ + "./typings.d.ts" + ] +} diff --git a/.storybook/typings.d.ts b/.storybook/typings.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f73d61b396c9c45ae43f02fbd587629524173156 --- /dev/null +++ b/.storybook/typings.d.ts @@ -0,0 +1,4 @@ +declare module '*.md' { + const content: string; + export default content; +} diff --git a/Dockerfile b/Dockerfile index 14bfaf866d6b8db773443eebc37b32f54300432d..17e735232f80890da6b5b2b65db1b33c152c9765 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,9 +3,6 @@ FROM node:14 as builder ARG BACKEND_URL ENV BACKEND_URL=${BACKEND_URL} -ARG DATASET_PREVIEW_URL -ENV DATASET_PREVIEW_URL=${DATASET_PREVIEW_URL:-https://hbp-kg-dataset-previewer.apps.hbp.eu/v2} - ARG BS_REST_URL ENV BS_REST_URL=${BS_REST_URL:-https://siibra-api-stable.apps.hbp.eu/v1_0} @@ -41,7 +38,8 @@ RUN rm -rf ./node_modules RUN npm i -RUN npm run build-aot +RUN npm run build +RUN npm run build-storybook # gzipping container FROM ubuntu:22.04 as compressor @@ -49,12 +47,19 @@ RUN apt upgrade -y && apt update && apt install brotli RUN mkdir /iv COPY --from=builder /iv/dist/aot /iv +COPY --from=builder /iv/storybook-static /iv/storybook-static + +# Remove duplicated assets. Use symlink instead. +WORKDIR /iv/storybook-static +RUN rm -rf ./assets +RUN ln -s ../assets ./assets + WORKDIR /iv RUN for f in $(find . -type f); do gzip < $f > $f.gz && brotli < $f > $f.br; done # prod container -FROM node:12-alpine +FROM node:14-alpine ENV NODE_ENV=production diff --git a/README.md b/README.md index 17600f0bf72bd06c4d1e823d71fce2d9644a5412..f54594f9017eb640d50275828d1d8924b8811846 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ Copyright 2020-2021, Forschungszentrum Jülich GmbH ## Getting Started -A live version of the Interactive Atlas Viewer is available at [https://interactive-viewer.apps.hbp.eu](https://interactive-viewer.apps.hbp.eu). This section is useful for developers who would like to develop this project. +A live version of the Interactive Atlas Viewer is available at [https://atlases.ebrains.eu/viewer/](https://atlases.ebrains.eu/viewer/). This section is useful for developers who would like to develop this project. ### General information -Interactive atlas viewer is built with [Angular (v12.0)](https://angular.io/), [Bootstrap (v4)](http://getbootstrap.com/), and [fontawesome icons](https://fontawesome.com/). Some other notable packages used are [ngrx/store](https://github.com/ngrx/platform) for state management. +Interactive atlas viewer is built with [Angular (v13.0)](https://angular.io/), [Bootstrap (v4)](http://getbootstrap.com/), and [fontawesome icons](https://fontawesome.com/). Some other notable packages used are [ngrx/store](https://github.com/ngrx/platform) for state management. Releases newer than [v0.2.9](https://github.com/HumanBrainProject/interactive-viewer/tree/v0.2.9) also uses a nodejs backend, which uses [passportjs](http://www.passportjs.org/) for user authentication, [express](https://expressjs.com/) as a http framework. @@ -25,7 +25,7 @@ Releases newer than [v0.2.9](https://github.com/HumanBrainProject/interactive-vi #### Prerequisites -- latest version of node 12.x.x or node 14.x.x +- node 12.20.0 or later #### Environments @@ -48,8 +48,8 @@ Please see [e2e_env.md](e2e_env.md) To run a dev server, run: ```bash -$ git clone https://github.com/HumanBrainProject/interactive-viewer -$ cd interactive-viewer +$ git clone https://github.com/FZJ-INM1-BDA/siibra-explorer +$ cd siibra-explorer $ npm i $ npm run dev-server ``` @@ -69,51 +69,7 @@ $ npm run build-aot ### Develop plugins -Below demonstrates an example workflow for developing plugins: - -```bash - -$ # build aot version of the atlas viewer -$ npm run build-aot -$ cd deploy -$ # run server with PLUGIN_URLS -$ PLUGIN_URLS=http://localhost:3333/manifest.json;http://localhost:3334/manifest.json node server.js -``` - -Interactive Atlas Viewer attempts to fetch list of manifests: - -``` -GET {BACKEND_URL}/plugins -``` - -The response from this endpoint will be: - -```json -[ - "http://localhost:3333/manifest.json", - "http://localhost:3334/manifest.json" -] -``` - -When user launches the viewer, the atlas viewer will attempt to fetch the metadata of the plugins: - -``` -GET http://localhost:3333/manifest.json -GET http://localhost:3334/manifest.json -``` - -The response from these endpoints are expected to adhere to [manifests](src/plugin_examples/README.md#Manifest%20JSON). - -When the user launches the plugin, the viewer will fetch `templateUrl` and `scriptUrl`, if necessary. - -Plugin developers can start their own webserver, use [interactive-viewer-plugin-template](https://github.com/HumanBrainProject/interactive-viewer-plugin-template), or (coming soon) provide link to a github repository. - - -[plugin readme](src/plugin_examples/README.md) - -[plugin api](src/plugin_examples/plugin_api.md) - -[plugin migration guide](src/plugin_examples/migrationGuide.md) +Please see [src/plugin/README.md](src/plugin/README.md) ## Contributing @@ -121,7 +77,7 @@ Feel free to raise an issue in this repo and/or file a PR. ## Versioning -Commit history prior to v0.2.0 is available in the [legacy-v0.2.0](https://github.com/HumanBrainProject/interactive-viewer/tree/legacy-v0.2.0) branch. The repo was rewritten to remove its dependency on neuroglancer and nehuba. This allowed for simpler webpack config, faster build time and AOT compilation. +Commit history prior to v0.2.0 is available in the [legacy-v0.2.0](https://github.com/FZJ-INM1-BDA/siibra-explorer/tree/legacy-v0.2.0) branch. The repo was rewritten to remove its dependency on neuroglancer and nehuba. This allowed for simpler webpack config, faster build time and AOT compilation. ## License diff --git a/build_env.md b/build_env.md index 1582bcdb0a487e3a91a88ecdcec45df445a687b9..1c29ea98614c3a40dbe91d7c620f9e6a5356b4e8 100644 --- a/build_env.md +++ b/build_env.md @@ -8,7 +8,6 @@ As interactive atlas viewer uses [webpack define plugin](https://webpack.js.org/ | `PRODUCTION` | if the build is for production, toggles optimisations such as minification | `undefined` | true | | `BACKEND_URL` | backend that the viewer calls to fetch available template spaces, parcellations, plugins, datasets | `null` | https://interactive-viewer.apps.hbp.eu/ | | `BS_REST_URL` | [siibra-api](https://github.com/FZJ-INM1-BDA/siibra-api) used to fetch different resources | https://siibra-api-stable.apps.hbp.eu/v1_0 | -| `DATASET_PREVIEW_URL` | dataset preview url used by component <https://github.com/fzj-inm1-bda/kg-dataset-previewer>. Useful for diagnosing issues with dataset previews.| https://hbp-kg-dataset-previewer.apps.hbp.eu/datasetPreview | http://localhost:1234/datasetPreview | | `MATOMO_URL` | base url for matomo analytics | `null` | https://example.com/matomo/ | | `MATOMO_ID` | application id for matomo analytics | `null` | 6 | | `STRICT_LOCAL` | hides **Explore** and **Download** buttons. Useful for offline demo's | `false` | `true` | diff --git a/common/constants.js b/common/constants.js index e36459f7a686904b39156256673842f9f8142814..94a42b81abf59142a3d50f5a175d64208e02fbbd 100644 --- a/common/constants.js +++ b/common/constants.js @@ -27,6 +27,7 @@ NO_BULK_DOWNLOAD: `No datasets pinned`, // overlay/layout specific + TOGGLE_DELINEATION: `Toggle delineation [q]`, SELECT_ATLAS: 'Atlas', CONTEXT_MENU: `Viewer context menu`, TOGGLE_FRONTAL_OCTANT: `Toggle perspective view frontal octant`, @@ -84,12 +85,32 @@ } exports.CONST = { + KG_TOS: `The interactive viewer queries HBP Knowledge Graph Data Platform ("KG") for published datasets. + + +Access to the data and metadata provided through KG requires that you cite and acknowledge said data and metadata according to the Terms and Conditions of the Platform. + + +Citation requirements are outlined <https://www.humanbrainproject.eu/en/explore-the-brain/search-terms-of-use#citations> . + + +Acknowledgement requirements are outlined <https://www.humanbrainproject.eu/en/explore-the-brain/search-terms-of-use#acknowledgements> + + +These outlines are based on the authoritative Terms and Conditions are found <https://www.humanbrainproject.eu/en/explore-the-brain/search-terms-of-use> + + +If you do not accept the Terms & Conditions you are not permitted to access or use the KG to search for, to submit, to post, or to download any materials found there-in. +`, + LOADING_TXT: `Loading ...`, CANNOT_DECIPHER_HEMISPHERE: 'Cannot decipher region hemisphere.', DOES_NOT_SUPPORT_MULTI_REGION_SELECTION: `Please only select a single region.`, MULTI_REGION_SELECTION: `Multi region selection`, + DESCRIPTION: 'Description', REGIONAL_FEATURES: 'Regional features', + CONNECTIVITY: 'Connectivity', NO_ADDIONTAL_INFO_AVAIL: `Currently, no additional information is linked to this region.`, ATLAS_NOT_FOUND: `Atlas not found. Maybe it is still loading. Try again in a few seconds?`, diff --git a/common/util.js b/common/util.js index 5f01f394ae6301fcca029647c3fef775b740bf74..198b3f0b6a0de651198849214120e918799e8af9 100644 --- a/common/util.js +++ b/common/util.js @@ -1,5 +1,7 @@ (function(exports) { + exports.camelToSnake = s => s.replace(/[A-Z]/g, s => `_${s.toLowerCase()}`) + const flattenReducer = (acc, curr) => acc.concat(curr) exports.flattenReducer = flattenReducer @@ -157,6 +159,8 @@ exports.flattenRegions = flattenRegions + exports.hexToRgb = hex => hex && hex.match(/[a-fA-F0-9]{2}/g).map(v => parseInt(v, 16)) + exports.rgbToHex = rgb => rgb && `#${rgb.map(color => color.toString(16).padStart(2, '0')).join("")}` exports.getRandomHex = (digit = 1024 * 1024 * 1024 * 1024) => Math.round(Math.random() * digit).toString(16) /** @@ -199,37 +203,6 @@ ) } - exports.serialiseParcellationRegion = ({ ngId, labelIndex }) => { - if (!ngId) { - throw new Error(`#serialiseParcellationRegion error: ngId must be defined`) - } - - if (!labelIndex) { - throw new Error(`#serialiseParcellationRegion error labelIndex must be defined`) - } - - return `${ngId}#${labelIndex}` - } - - const deserialiseParcRegionId = labelIndexId => { - const _ = labelIndexId && labelIndexId.split && labelIndexId.split('#') || [] - const ngId = _.length > 1 - ? _[0] - : null - const labelIndex = _.length > 1 - ? Number(_[1]) - : _.length === 0 - ? null - : Number(_[0]) - return { labelIndex, ngId } - } - - exports.deserialiseParcRegionId = deserialiseParcRegionId - - exports.deserialiseParcellationRegion = ({ region, labelIndexId, inheritedNgId = 'root' }) => { - const { labelIndex, ngId } = deserialiseParcRegionId(labelIndexId) - } - const getPad = ({ length, pad }) => { if (pad.length !== 1) throw new Error(`pad needs to be precisely 1 character`) return input => { @@ -252,4 +225,19 @@ return `${year}${pad2(month)}${pad2(date)}_${pad2(hr)}${pad2(min)}` } + function isVec4(input) { + if (!Array.isArray(input)) return false + if (input.length !== 4) return false + return input.every(v => typeof v === "number") + } + function isMat4(input) { + if (!Array.isArray(input)) return false + if (input.length !== 4) return false + return input.every(isVec4) + } + + exports.isVec4 = isVec4 + exports.isMat4 = isMat4 + + })(typeof exports === 'undefined' ? module.exports : exports) diff --git a/deploy/bkwdCompat/urlState.js b/deploy/bkwdCompat/urlState.js index 66f92c3dc4f6b375fcfe0c70f16a685b43c30e0d..64093f9e035e19fc95c9ee8eea8b2caa2303912b 100644 --- a/deploy/bkwdCompat/urlState.js +++ b/deploy/bkwdCompat/urlState.js @@ -114,8 +114,12 @@ const WARNING_STRINGS = { REGION_SELECT_ERROR: 'Region selected cannot be processed properly.', TEMPLATE_ERROR: 'Template not found.', } - +const pliPreviewUrl = `/a:juelich:iav:atlas:v1.0.0:1/t:minds:core:referencespace:v1.0.0:a1655b99-82f1-420f-a3c2-fe80fd4c8588/p:juelich:iav:atlas:v1.0.0:4/@:0.0.0.-W000.._eCwg.2-FUe3._-s_W.2_evlu..7LIy..1qI1a.D31U~.i-Os~..HRE/f:siibra:features:voi:19c437087299dd62e7c507200f69aea6` module.exports = (query, _warningCb) => { + + const HOST_PATHNAME = process.env.HOST_PATHNAME || '' + let redirectUrl = `${HOST_PATHNAME}/#` + const { standaloneVolumes, niftiLayers, // deprecating - check if anyone calls this url @@ -163,7 +167,17 @@ module.exports = (query, _warningCb) => { if (Array.isArray(parsedDsp)) { if (parsedDsp.length === 1) { const { datasetId, filename } = parsedDsp[0] - dsp = `/dsp:${encodeId(datasetId)}::${encodeURI(filename)}` + if (datasetId === "minds/core/dataset/v1.0.0/b08a7dbc-7c75-4ce7-905b-690b2b1e8957") { + /** + * if preview pli link, return hardcoded link + */ + return `${HOST_PATHNAME}/#${pliPreviewUrl}` + } else { + /** + * TODO deprecate dsp + */ + dsp = `/dsp:${encodeId(datasetId)}::${encodeURI(filename)}` + } } else { searchParam.set(`previewingDatasetFiles`, previewingDatasetFiles) } @@ -206,8 +220,6 @@ module.exports = (query, _warningCb) => { // ignore region selected and move on } } - const HOST_PATHNAME = process.env.HOST_PATHNAME || '' - let redirectUrl = `${HOST_PATHNAME}/#` if (standaloneVolumes) { searchParam.set('standaloneVolumes', standaloneVolumes) if (nav) redirectUrl += nav diff --git a/deploy/csp/index.js b/deploy/csp/index.js index 0ed247afe51b6e6d361eb0ea09ecdc319ddc5ad5..898228ce845b95c747d027704c08ab96f8196c30 100644 --- a/deploy/csp/index.js +++ b/deploy/csp/index.js @@ -54,7 +54,9 @@ const connectSrc = [ 'object.cscs.ch', // required for dataset previews - 'hbp-kg-dataset-previewer.apps.hbp.eu/v2/', + + // spatial transform + "hbp-spatial-backend.apps.hbp.eu", // injected by env var ...CSP_CONNECT_SRC @@ -102,26 +104,24 @@ module.exports = { ], imgSrc: [ "'self'", - "hbp-kg-dataset-previewer.apps.hbp.eu/v2/" ], scriptSrc:[ "'self'", ...userScriptSrc, - 'code.jquery.com', // plugin load external library -> jquery v2 and v3 - 'cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/', // plugin load external library -> web components - 'cdnjs.cloudflare.com/ajax/libs/d3/', // plugin load external lib -> d3 - 'cdn.jsdelivr.net/npm/vue@2.5.16/', // plugin load external lib -> vue 2 - 'cdn.jsdelivr.net/npm/preact@8.4.2/', // plugin load external lib -> preact - 'unpkg.com/react@16/umd/', // plugin load external lib -> react 'unpkg.com/kg-dataset-previewer@1.2.0/', // preview component + 'cdnjs.cloudflare.com/ajax/libs/d3/', // required for preview component 'cdnjs.cloudflare.com/ajax/libs/mathjax/', // math jax - 'https://unpkg.com/three-surfer@0.0.10/dist/bundle.js', // for threeSurfer (freesurfer support in browser) + 'https://unpkg.com/three-surfer@0.0.11/dist/bundle.js', // for threeSurfer (freesurfer support in browser) 'https://unpkg.com/ng-layer-tune@0.0.6/dist/ng-layer-tune/', // needed for ng layer control + 'https://unpkg.com/hbp-connectivity-component@0.6.2/', // needed for connectivity component (req, res) => res.locals.nonce ? `'nonce-${res.locals.nonce}'` : null, ...SCRIPT_SRC, ...WHITE_LIST_SRC, ...defaultAllowedSites ], + frameSrc: [ + "*" + ], reportUri: CSP_REPORT_URI || '/report-violation' }, reportOnly diff --git a/deploy/package-lock.json b/deploy/package-lock.json index ba5432458b504ab7a4e11b768a0d695442113ee7..1dac062d78f8ca3d5a23d9456457a913ceef919c 100644 --- a/deploy/package-lock.json +++ b/deploy/package-lock.json @@ -151,77 +151,6 @@ "picomatch": "^2.0.4" } }, - "archiver": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-3.1.1.tgz", - "integrity": "sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==", - "requires": { - "archiver-utils": "^2.1.0", - "async": "^2.6.3", - "buffer-crc32": "^0.2.1", - "glob": "^7.1.4", - "readable-stream": "^3.4.0", - "tar-stream": "^2.1.0", - "zip-stream": "^2.1.2" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -252,21 +181,6 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - }, - "dependencies": { - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - } - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -285,7 +199,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "bcrypt-pbkdf": { "version": "1.0.2", @@ -301,26 +216,6 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "bl": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.1.tgz", - "integrity": "sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ==", - "requires": { - "readable-stream": "^3.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -393,6 +288,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -413,11 +309,6 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" - }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -583,21 +474,11 @@ "delayed-stream": "~1.0.0" } }, - "compress-commons": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz", - "integrity": "sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==", - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^3.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^2.3.6" - } - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "connect-redis": { "version": "5.1.0", @@ -661,27 +542,6 @@ "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz", "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" }, - "crc32-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", - "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -1000,15 +860,11 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "2.3.2", @@ -1089,11 +945,6 @@ "type-fest": "^0.9.0" } }, - "graceful-fs": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", - "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==" - }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -1191,6 +1042,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -1341,51 +1193,18 @@ "json-buffer": "3.0.1" } }, - "lazystream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "requires": { - "readable-stream": "^2.0.5" - } - }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" - }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -1498,6 +1317,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1686,7 +1506,8 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "normalize-url": { "version": "4.5.1", @@ -1853,7 +1674,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-to-regexp": { "version": "0.1.7", @@ -2351,30 +2173,6 @@ "has-flag": "^4.0.0" } }, - "tar-stream": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.0.tgz", - "integrity": "sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==", - "requires": { - "bl": "^3.0.0", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, "through2": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", @@ -2619,28 +2417,6 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true - }, - "zip-stream": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.2.tgz", - "integrity": "sha512-ykebHGa2+uzth/R4HZLkZh3XFJzivhVsjJt8bN3GvBzLaqqrUdRacu+c4QtnUgjkkQfsOuNE1JgLKMCPNmkKgg==", - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^2.1.1", - "readable-stream": "^3.4.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } } } } diff --git a/deploy/package.json b/deploy/package.json index f96b15c4c77473c9ec9874e57fd6638bce1100e4..333568cdfc0b2804ed97319a6b00dd1577c59d50 100644 --- a/deploy/package.json +++ b/deploy/package.json @@ -12,7 +12,6 @@ "author": "", "license": "ISC", "dependencies": { - "archiver": "^3.0.0", "body-parser": "^1.19.0", "connect-redis": "^5.0.0", "cookie-parser": "^1.4.5", diff --git a/deploy/plugins/index.js b/deploy/plugins/index.js index 4618bce4676259fb966d5770653aa03e3380db6d..028a0158d9a94b4a353242099c9cfed7eb8f867f 100644 --- a/deploy/plugins/index.js +++ b/deploy/plugins/index.js @@ -7,24 +7,26 @@ const express = require('express') const lruStore = require('../lruStore') const { race } = require("../../common/util") const got = require('got') +const { URL } = require('url') +const path = require("path") const router = express.Router() -const DEV_PLUGINS = (() => { +const V2_7_DEV_PLUGINS = (() => { try { return JSON.parse( - process.env.DEV_PLUGINS || `[]` + process.env.V2_7_DEV_PLUGINS || `[]` ) } catch (e) { console.warn(`Parsing DEV_PLUGINS failed: ${e}`) return [] } })() -const PLUGIN_URLS = (process.env.PLUGIN_URLS && process.env.PLUGIN_URLS.split(';')) || [] -const STAGING_PLUGIN_URLS = (process.env.STAGING_PLUGIN_URLS && process.env.STAGING_PLUGIN_URLS.split(';')) || [] +const V2_7_PLUGIN_URLS = (process.env.V2_7_PLUGIN_URLS && process.env.V2_7_PLUGIN_URLS.split(';')) || [] +const V2_7_STAGING_PLUGIN_URLS = (process.env.V2_7_STAGING_PLUGIN_URLS && process.env.V2_7_STAGING_PLUGIN_URLS.split(';')) || [] router.get('', (_req, res) => { return res.status(200).json([ - ...PLUGIN_URLS, - ...STAGING_PLUGIN_URLS + ...V2_7_PLUGIN_URLS, + ...V2_7_STAGING_PLUGIN_URLS ]) }) @@ -32,40 +34,50 @@ const getKey = url => `plugin:manifest-cache:${url}` router.get('/manifests', async (_req, res) => { - const output = [] - for (const plugin of [ ...PLUGIN_URLS, ...STAGING_PLUGIN_URLS ]) { - try { - await race(async () => { - const key = getKey(plugin) - try { - const result = await race(async () => { + const allManifests = await Promise.all( + [...V2_7_PLUGIN_URLS, ...V2_7_STAGING_PLUGIN_URLS].map(async url => { + try { + return await race( + async () => { + const key = getKey(url) + await lruStore._initPr const { store } = lruStore - const storedManifest = await store.get(key) - if (storedManifest) { - return JSON.parse(storedManifest) - } else { - throw `not found` + + try { + const storedManifest = await store.get(key) + if (storedManifest) return JSON.parse(storedManifest) + else throw `not found` + } catch (e) { + const resp = await got(url) + const json = JSON.parse(resp.body) + + const { iframeUrl, 'siibra-explorer': flag } = json + if (!flag) return null + if (!iframeUrl) return null + const u = new URL(url) + + let replaceObj = {} + if (!/^https?:\/\//.test(iframeUrl)) { + u.pathname = path.resolve(path.dirname(u.pathname), iframeUrl) + replaceObj['iframeUrl'] = u.toString() + } + const returnObj = {...json, ...replaceObj} + await store.set(key, JSON.stringify(returnObj), { maxAge: 1000 * 60 * 60 }) + return returnObj } - }, { timeout: 100 }) - output.push(result) - } catch (e) { - const resp = await got(plugin) - const json = JSON.parse(resp.body) - - output.push(json) - store.set(key, JSON.stringify(json), { maxAge: 1000 * 60 * 60 }) - .catch(e => console.error(`setting store value error`, e)) - } - }) - } catch (e) { - console.error(`racing to get manifest ${plugin} timed out or errored.`, e) - } - } - + }, + { timeout: 1000 } + ) + } catch (e) { + console.error(`fetching manifest error: ${e}`) + return null + } + }) + ) res.status(200).json( - [...DEV_PLUGINS, ...output] + [...V2_7_DEV_PLUGINS, ...allManifests.filter(v => !!v)] ) }) diff --git a/deploy/quickstart/index.js b/deploy/quickstart/index.js index 0ca183d324eb24ac413d16ba31633eee2e5cf4bb..ceef857a7d4db225eac74ae82d47a354f66e9d39 100644 --- a/deploy/quickstart/index.js +++ b/deploy/quickstart/index.js @@ -28,7 +28,7 @@ const getQuickStartMdPr = (async () => { <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Interactive Atlas Viewer Quickstart</title> </head> -<body class="p-4"> +<body class="sxplr-p-4"> </body> <script> diff --git a/deploy/saneUrl/index.js b/deploy/saneUrl/index.js index 9f732525722a4f7d21c8475748f1db3874230ce4..3bc0bca9fe6e6eafad1075a0a4b16b2eddddd91e 100644 --- a/deploy/saneUrl/index.js +++ b/deploy/saneUrl/index.js @@ -66,7 +66,13 @@ router.get('/:name', async (req, res) => { if (redirectFlag) { if (queryString) return res.redirect(`${REAL_HOSTNAME}?${queryString}`) - if (hashPath) return res.redirect(`${REAL_HOSTNAME}#${hashPath}/${xtraRoutes.join('/')}`) + if (hashPath) { + let redirectUrl = `${REAL_HOSTNAME}#${hashPath}` + if (xtraRoutes.length > 0) { + redirectUrl += `/${xtraRoutes.join('/')}` + } + return res.redirect(redirectUrl) + } } else { return res.status(200).send(json) } diff --git a/docs/releases/v2.7.0.md b/docs/releases/v2.7.0.md new file mode 100644 index 0000000000000000000000000000000000000000..1ce6c8632654266ab4931d94f3f47a630db3f941 --- /dev/null +++ b/docs/releases/v2.7.0.md @@ -0,0 +1,14 @@ +# v2.7.0 + +## Breaking changes + +- plugin in interactive atlas viewer has been completely redesigned + +## New feature + +- added storybook for component development +- Add first implementation of fetching VOI from siibra-api + +## Under the hood + +- Major refactor to use new siibra-api schema diff --git a/e2e/checklist.md b/e2e/checklist.md index b51cd1cdc38ce354cf1bc0ca97a6d06e9b3896b4..b1a0c2ce262eb278c84724a0c680404b6c512fc6 100644 --- a/e2e/checklist.md +++ b/e2e/checklist.md @@ -72,3 +72,4 @@ - [ ] [vipUrl](https://atlases.ebrains.eu/viewer-staging/mouse) redirects allen mouse 2017 ## plugins - [ ] jugex plugin works +- [ ] 1um section works diff --git a/mkdocs.yml b/mkdocs.yml index 3fb53b576cae2ce147774c62caf060968a2a8901..cf6e2cb584ed320eac04af6ce43ceed9753165f0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -33,6 +33,7 @@ nav: - Fetching datasets: 'advanced/datasets.md' - Display non-atlas volumes: 'advanced/otherVolumes.md' - Release notes: + - v2.7.0: 'releases/v2.7.0.md' - v2.6.10: 'releases/v2.6.10.md' - v2.6.9: 'releases/v2.6.9.md' - v2.6.8: 'releases/v2.6.8.md' diff --git a/package-lock.json b/package-lock.json index d2cb14c3526da60907ff27c8338e60bddcd69e90..a430b1fb3e4bb0ca1d9daea0579ba57778c1315c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,14 @@ { "name": "interactive-viewer", - "version": "2.6.8", - "lockfileVersion": 1, "requires": true, + "lockfileVersion": 1, "dependencies": { + "@aduh95/viz.js": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@aduh95/viz.js/-/viz.js-3.6.0.tgz", + "integrity": "sha512-ywd9QYyLByJvkdxwIB4ve4ikF8T2AUXSIn0EBYSlbv1TxnAshgPXiE4JWk6d0LJHWDsxR7Piw7TwBcQ/YrgeCA==", + "dev": true + }, "@ampproject/remapping": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-1.0.1.tgz", @@ -15,26 +20,26 @@ } }, "@angular-devkit/architect": { - "version": "0.1202.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1202.13.tgz", - "integrity": "sha512-LXgiidXwBgyWPqqWK4xR1/kCPQTMTzG5w+s7+LvENUZwbcdl6CKrOMjfgjo6WPr6yeq+WWQvPCD4pZ6nXRTm7A==", + "version": "0.1202.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1202.17.tgz", + "integrity": "sha512-uUQcHcLbPvr9adALQSLU1MTDduVUR2kZAHi2e7SmL9ioel84pPVXBoD0WpSBeUMKwPiDs3TQDaxDB49hl0nBSQ==", "dev": true, "requires": { - "@angular-devkit/core": "12.2.13", + "@angular-devkit/core": "12.2.17", "rxjs": "6.6.7" } }, "@angular-devkit/build-angular": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-12.2.13.tgz", - "integrity": "sha512-iJ1P/RZ1hk2n/HtEqB5ohXvHa+Hf0BXShYskSGrn6/klcTb0eJTCREsFxHk7mNEmRIGPWkjbLAslqpPgwiagXg==", + "version": "12.2.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-12.2.17.tgz", + "integrity": "sha512-uc3HGHVQyatqQ/M53oxYBvhz0jx0hgdc7WT+L56GLHvgz7Ct2VEbpWaMfwHkFfE1F1iHkIgnTKHKWacJl1yQIg==", "dev": true, "requires": { "@ampproject/remapping": "1.0.1", - "@angular-devkit/architect": "0.1202.13", - "@angular-devkit/build-optimizer": "0.1202.13", - "@angular-devkit/build-webpack": "0.1202.13", - "@angular-devkit/core": "12.2.13", + "@angular-devkit/architect": "0.1202.17", + "@angular-devkit/build-optimizer": "0.1202.17", + "@angular-devkit/build-webpack": "0.1202.17", + "@angular-devkit/core": "12.2.17", "@babel/core": "7.14.8", "@babel/generator": "7.14.8", "@babel/helper-annotate-as-pure": "7.14.5", @@ -46,7 +51,7 @@ "@babel/template": "7.14.5", "@discoveryjs/json-ext": "0.5.3", "@jsdevtools/coverage-istanbul-loader": "3.0.5", - "@ngtools/webpack": "12.2.13", + "@ngtools/webpack": "12.2.17", "ansi-colors": "4.1.1", "babel-loader": "8.2.2", "browserslist": "^4.9.1", @@ -97,11 +102,76 @@ "tslib": "2.3.0", "webpack": "5.50.0", "webpack-dev-middleware": "5.0.0", - "webpack-dev-server": "3.11.2", + "webpack-dev-server": "3.11.3", "webpack-merge": "5.8.0", "webpack-subresource-integrity": "1.5.2" }, "dependencies": { + "@babel/core": { + "version": "7.14.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.8.tgz", + "integrity": "sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.8", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.8", + "@babel/helpers": "^7.14.8", + "@babel/parser": "^7.14.8", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.8", + "@babel/types": "^7.14.8", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-loader": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", + "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^1.4.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, "esbuild": { "version": "0.13.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.8.tgz", @@ -150,6 +220,23 @@ "source-map-js": "^0.6.2" } }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, "source-map-js": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", @@ -165,9 +252,9 @@ } }, "@angular-devkit/build-optimizer": { - "version": "0.1202.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.1202.13.tgz", - "integrity": "sha512-XX6rX5+mAl+MiIJDvi5N5mBLWOoskhMJ5r/G1PEqv0CMMJSSw60zUTndjxfq/nrX0PtsV3j/aqHN4Sj0w/gumg==", + "version": "0.1202.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.1202.17.tgz", + "integrity": "sha512-1qWGWw7cCNADB4LZ/zjiSK0GLmr2kebYyNG0KutCE8GNVxv2h6w6dJP6t1C/BgskRuBPCAhvE+lEKN8ljSutag==", "dev": true, "requires": { "source-map": "0.7.3", @@ -184,19 +271,19 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.1202.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1202.13.tgz", - "integrity": "sha512-KafzGyHuU2gBKaSICfMTFP2niTYZ/46DKU94TQ0lCILdJZsj0NE5M/288LSCbYgu2c7srJKz+Rvb+JcYGxIZ1g==", + "version": "0.1202.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1202.17.tgz", + "integrity": "sha512-z7FW43DJ4p8UZwbFRmMrh2ohqhI2Wtdg3+FZiTnl4opb3zYheGiNxPlTuiyKjG21JUkGCdthkkBLCNfaUU0U/Q==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1202.13", + "@angular-devkit/architect": "0.1202.17", "rxjs": "6.6.7" } }, "@angular-devkit/core": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.2.13.tgz", - "integrity": "sha512-9csMF0p+lTvlWnutxxTZ/+pDRMIbXk/TV4MGLbcqUPPfeG3dCRwErns73xLuMTwp9qO/KCLkFqNaM6cGOoqsDA==", + "version": "12.2.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-12.2.17.tgz", + "integrity": "sha512-PyOY7LGUPPd6rakxUYbfQN6zAdOCMCouVp5tERY1WTdMdEiuULOtHsPee8kNbh75pD59KbJNU+fwozPRMuIm5g==", "dev": true, "requires": { "ajv": "8.6.2", @@ -228,20 +315,20 @@ } }, "@angular-devkit/schematics": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.2.13.tgz", - "integrity": "sha512-LQTv72R5Ma1uowMEeii2wIoDWI4bYQyZvunqPy9jRveBTjli2yVwwcOziGCVyttwlYs46bSdxThgeEvVIako2w==", + "version": "12.2.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-12.2.17.tgz", + "integrity": "sha512-c0eNu/nx1Mnu7KcZgYTYHP736H4Y9pSyLBSmLAHYZv3t3m0dIPbhifRcLQX7hHQ8fGT2ZFxmOpaQG5/DcIghSw==", "dev": true, "requires": { - "@angular-devkit/core": "12.2.13", + "@angular-devkit/core": "12.2.17", "ora": "5.4.1", "rxjs": "6.6.7" } }, "@angular/animations": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.13.tgz", - "integrity": "sha512-qpdmvu+nxsKnimJ3Hc1joNuzK7xXYyE+VtNMk4K77Ao/9+5C/juGMce85DhqZCcu1xraZ3YYIrzYmL/GgdUK4g==", + "version": "12.2.16", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.16.tgz", + "integrity": "sha512-Kf6C7Ta+fCMq5DvT9JNVhBkcECrqFa3wumiC6ssGo5sNaEzXz+tlep9ZgEbqfxSn7gAN7L1DgsbS9u0O6tbUkg==", "requires": { "tslib": "^2.2.0" } @@ -256,15 +343,15 @@ } }, "@angular/cli": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.2.13.tgz", - "integrity": "sha512-Yz6MuwdxxP6U2i8iRuCSNZeJxlLDPshT/joymCsFdjwSMGEH9Kk9DdvAfFYfzdHGdHbGrDdASJ4G+uALyNSLxw==", + "version": "12.2.17", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.2.17.tgz", + "integrity": "sha512-mubRPp5hRIK/q0J8q6kVAqbYYuBUKMMBljUCqT4fHsl+qXYD27rgG3EqNzycKBMHUIlykotrDSdy47voD+atOg==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1202.13", - "@angular-devkit/core": "12.2.13", - "@angular-devkit/schematics": "12.2.13", - "@schematics/angular": "12.2.13", + "@angular-devkit/architect": "0.1202.17", + "@angular-devkit/core": "12.2.17", + "@angular-devkit/schematics": "12.2.17", + "@schematics/angular": "12.2.17", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.2", @@ -275,7 +362,7 @@ "npm-pick-manifest": "6.1.1", "open": "8.2.1", "ora": "5.4.1", - "pacote": "11.3.5", + "pacote": "12.0.2", "resolve": "1.20.0", "semver": "7.3.5", "symbol-observable": "4.0.0", @@ -291,34 +378,38 @@ "ms": "2.1.2" } }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } } } }, "@angular/common": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.13.tgz", - "integrity": "sha512-I1t/jl9ojCwTJKT7PKHnk23D+vMHTHS/9qZ2nndCjzGusMK8029nn8l3tHAkwefvxZWSaLAk1MgsVcP+rHQNsQ==", + "version": "12.2.16", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.16.tgz", + "integrity": "sha512-FEqTXTEsnbDInqV1yFlm97Tz1OFqZS5t0TUkm8gzXRgpIce/F/jLwAg0u1VQkgOsno6cNm0xTWPoZgu85NI4ug==", "requires": { "tslib": "^2.2.0" } }, "@angular/compiler": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.13.tgz", - "integrity": "sha512-L0saTTJJtxldjhaGIL6b1BCfodPOEz4Wrev3pEUK5UcODooj5HLiE/aO6jiNb8M4XTbdqByKyqvZyWzGHeexVQ==", + "version": "12.2.16", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.16.tgz", + "integrity": "sha512-nsYEw+yu8QyeqPf9nAmG419i1mtGM4v8+U+S3eQHQFXTgJzLymMykWHYu2ETdjUpNSLK6xcIQDBWtWnWSfJjAA==", "requires": { "tslib": "^2.2.0" } }, "@angular/compiler-cli": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-12.2.13.tgz", - "integrity": "sha512-qmnmihl3SCRooj/mCsNIZLtnQ6qbx1/L6aMIEQosPvQhMeGMt8GCYvQPE8IZ+sahv7fih95HCWNa9TeLpOylug==", + "version": "12.2.16", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-12.2.16.tgz", + "integrity": "sha512-tlalh8SJvdCWbUPRUR5GamaP+wSc/GuCsoUZpSbcczGKgSlbaEVXUYtVXm8/wuT6Slk2sSEbRs7tXGF2i7qxVw==", "dev": true, "requires": { "@babel/core": "^7.8.6", @@ -337,12 +428,6 @@ "yargs": "^17.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -378,44 +463,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -434,40 +487,40 @@ "dev": true }, "yargs": { - "version": "17.2.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz", - "integrity": "sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==", + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.0.0" } }, "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", "dev": true } } }, "@angular/core": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.13.tgz", - "integrity": "sha512-tZ5nAnmOi418JDaJIFiiP9z2JrluMJZvUvXO4QDUs52BXaL2yuP7MJ2LczWOVJXrBLZXeZGfjDjZmaFPA24grg==", + "version": "12.2.16", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.16.tgz", + "integrity": "sha512-jsmvaRdAfng99z2a9mAmkfcsCE1wm+tBYVDxnc5JquSXznwtncjzcoc2X0J0dzrkCDvzFfpTsZ9vehylytBc+A==", "requires": { "tslib": "^2.2.0" } }, "@angular/forms": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.13.tgz", - "integrity": "sha512-wa7I5R8sck2q+VWNL262GJTVxtpEHMys8Bt6oE+lyHB5zlZAgOXwAb8GE4XVwuc+BZU1Gvrocn21P/8KvDY1uw==", + "version": "12.2.16", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.16.tgz", + "integrity": "sha512-sb+gpNun5aN7CZfHXS6X7vJcd/0A1P/gRBZpYtQTzBYnqEFCOFIvR62eb05aHQ4JhgKaSPpIXrbz/bAwY/njZw==", "requires": { "tslib": "^2.2.0" } @@ -481,25 +534,25 @@ } }, "@angular/platform-browser": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.13.tgz", - "integrity": "sha512-Ua8m2GtG2msqz/Mr/MK7s8RXud554x8cup2THVAwetyTaY5th/1LOZX0hhDIhfsxBCLHnC53LRhMbSsI0cikOg==", + "version": "12.2.16", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.16.tgz", + "integrity": "sha512-T855ppLeQO6hRHi7lGf5fwPoUVt+c0h2rgkV5jHElc3ylaGnhecmZc6fnWLX4pw82TMJUgUV88CY8JCFabJWwg==", "requires": { "tslib": "^2.2.0" } }, "@angular/platform-browser-dynamic": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.13.tgz", - "integrity": "sha512-a7y7R3vOXhMAk9uQWK5/23DefahuF0UYJSFM/wKeVo5zR+qOCVCTfafkJMcWbuZWTrSDbVafJ1xbcWnu3+TkCg==", + "version": "12.2.16", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.16.tgz", + "integrity": "sha512-XGxoACAMW/bc3atiVRpaiYwU4LkobYwVzwlxTT/BxOfsdt8ILb5wU8Fx1TMKNECOQHSGdK0qqhch4pTBZ3cb2g==", "requires": { "tslib": "^2.2.0" } }, "@angular/router": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.13.tgz", - "integrity": "sha512-HZL/Pzp6I7fQiMLrzfEzhnqhgNcGcFjBgMMOoLp5IA1IV26rp1NU6zYJzPrXtopBx7XLl8BECehAwFqrJsu/PQ==", + "version": "12.2.16", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.16.tgz", + "integrity": "sha512-LuFXSMIvX/VrB4jbYhigG2Y2pGQ9ULsSBUwDWwQCf4kr0eVI37LBJ2Vr74GBEznjgQ0UmWE89E+XYI80UhERTw==", "requires": { "tslib": "^2.2.0" } @@ -511,54 +564,103 @@ "dev": true }, "@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { - "@babel/highlight": "^7.16.0" + "@babel/highlight": "^7.16.7" } }, "@babel/compat-data": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz", - "integrity": "sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", + "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", "dev": true }, "@babel/core": { - "version": "7.14.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.8.tgz", - "integrity": "sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.8", - "@babel/helper-compilation-targets": "^7.14.5", - "@babel/helper-module-transforms": "^7.14.8", - "@babel/helpers": "^7.14.8", - "@babel/parser": "^7.14.8", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.8", - "@babel/types": "^7.14.8", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.0.tgz", + "integrity": "sha512-Xyw74OlJwDijToNi0+6BBI5mLLR5+5R3bcSH80LXzjzEGEUlvNzujEE71BaD/ApEZHAvFI/Mlmp4M5lIkdeeWw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.0", + "@babel/helper-compilation-targets": "^7.17.10", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helpers": "^7.18.0", + "@babel/parser": "^7.18.0", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.0", + "@babel/types": "^7.18.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" + "json5": "^2.2.1", + "semver": "^6.3.0" }, "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/generator": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.0.tgz", + "integrity": "sha512-81YO9gGx6voPXlvYdZBliFXAZU8vZ9AZ6z+CjlmcnaeOcYSFbMTpdeDUO9xD9dh/68Vq03I8ZspfUTPfitcDHg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.0", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", + "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, @@ -591,24 +693,24 @@ } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.0.tgz", - "integrity": "sha512-9KuleLT0e77wFUku6TUkqZzCEymBdtuQQ27MhEKzf9UOOJu3cYj98kyaDAzxpC7lV6DGiZFuC8XqDsq8/Kl6aQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", + "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/helper-explode-assignable-expression": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/helper-compilation-targets": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", - "integrity": "sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz", + "integrity": "sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.16.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.17.5", + "@babel/compat-data": "^7.17.10", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.20.2", "semver": "^6.3.0" }, "dependencies": { @@ -621,47 +723,48 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.0.tgz", - "integrity": "sha512-XLwWvqEaq19zFlF5PTgOod4bUA+XbkR4WLQBct1bkzmxJGB0ZEJaoKF4c8cgH9oBtCDuYJ8BP5NB9uFiEgO5QA==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-member-expression-to-functions": "^7.16.0", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/helper-replace-supers": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0" + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz", + "integrity": "sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-member-expression-to-functions": "^7.17.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" }, "dependencies": { "@babel/helper-annotate-as-pure": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", - "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } } } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz", - "integrity": "sha512-3DyG0zAFAZKcOp7aVr33ddwkxJ0Z0Jr5V99y3I690eYLpukJsJvAbzTy1ewoCqsML8SbIrjH14Jc/nSQ4TvNPA==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz", + "integrity": "sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "regexpu-core": "^4.7.1" + "@babel/helper-annotate-as-pure": "^7.16.7", + "regexpu-core": "^5.0.1" }, "dependencies": { "@babel/helper-annotate-as-pure": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", - "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } } } @@ -690,160 +793,160 @@ } } }, + "@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, "@babel/helper-explode-assignable-expression": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz", - "integrity": "sha512-Hk2SLxC9ZbcOhLpg/yMznzJ11W++lg5GMbxt1ev6TXUiJB0N42KPC+7w8a+eWGuqDnUYuwStJoZHM7RgmIOaGQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", + "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/template": "^7.16.7", + "@babel/types": "^7.17.0" }, "dependencies": { "@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" } } } }, - "@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, "@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz", - "integrity": "sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", + "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.17.0" } }, "@babel/helper-module-imports": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", - "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-module-transforms": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz", - "integrity": "sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.16.0", - "@babel/helper-replace-supers": "^7.16.0", - "@babel/helper-simple-access": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", + "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.0", + "@babel/types": "^7.18.0" }, "dependencies": { "@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" } } } }, "@babel/helper-optimise-call-expression": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", - "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz", + "integrity": "sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==", "dev": true }, "@babel/helper-remap-async-to-generator": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.4.tgz", - "integrity": "sha512-vGERmmhR+s7eH5Y/cp8PCVzj4XEjerq8jooMfxFdA5xVtAk9Sh4AQsrWgiErUEBjtGrBtOFKDUcWQFW4/dFwMA==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", + "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-wrap-function": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-wrap-function": "^7.16.8", + "@babel/types": "^7.16.8" }, "dependencies": { "@babel/helper-annotate-as-pure": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", - "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } } } }, "@babel/helper-replace-supers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz", - "integrity": "sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.16.0", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/helper-simple-access": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", - "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.17.0" } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -856,101 +959,110 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true }, "@babel/helper-wrap-function": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.0.tgz", - "integrity": "sha512-VVMGzYY3vkWgCJML+qVLvGIam902mJW0FvT7Avj1zEe0Gn7D93aWdLblYARTxEw+6DhZmtzhBM2zv0ekE5zg1g==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", + "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/helper-function-name": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8" }, "dependencies": { "@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" } } } }, "@babel/helpers": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.3.tgz", - "integrity": "sha512-Xn8IhDlBPhvYTvgewPKawhADichOsbkZuzN7qz2BusOM0brChsyXMDJvldWaYMMUNiCQdQzNEioXTp3sC8Nt8w==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.0.tgz", + "integrity": "sha512-AE+HMYhmlMIbho9nbvicHyxFwhrO+xhKB6AhRxzl8w46Yj0VXTZjEsAoBVC7rB2I0jzX+yWyVybnO08qkfx6kg==", "dev": true, "requires": { - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.3", - "@babel/types": "^7.16.0" + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.18.0", + "@babel/types": "^7.18.0" }, "dependencies": { "@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" } } } }, "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", + "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz", - "integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-AqDccGC+m5O/iUStSJy3DGRIUFu7WbY/CppZYwrEUB4N0tZlnI8CSTsgL7v5fHVFmUbRv2sd+yy27o8Ydt4MGg==", "dev": true }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.17.12.tgz", + "integrity": "sha512-xCJQXl4EeQ3J9C4yOmpTrtVGmzpm2iSzyxbkZHw7UCnZBftHpF/hpII80uWVyVrc40ytIClHjgWGTG1g/yB+aw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.0.tgz", - "integrity": "sha512-4tcFwwicpWTrpl9qjf7UsoosaArgImF85AxqCRZlgc3IQDvkUHjJpruXAL58Wmj+T6fypWTC/BakfEkwIL/pwA==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.17.12.tgz", + "integrity": "sha512-/vt0hpIw0x4b6BLKUkwlvEoiGZYYLNZ96CzyHYPbtG2jZGz6LBe7/V+drYrc/d+ovrF9NBi0pmtvmNb/FsWtRQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.0" + "@babel/plugin-proposal-optional-chaining": "^7.17.12" } }, "@babel/plugin-proposal-async-generator-functions": { @@ -965,161 +1077,185 @@ } }, "@babel/plugin-proposal-class-properties": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.0.tgz", - "integrity": "sha512-mCF3HcuZSY9Fcx56Lbn+CGdT44ioBMMvjNVldpKtj8tpniETdLjnxdHI1+sDWXIM1nNt+EanJOZ3IG9lzVjs7A==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz", + "integrity": "sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-proposal-class-static-block": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.0.tgz", - "integrity": "sha512-mAy3sdcY9sKAkf3lQbDiv3olOfiLqI51c9DR9b19uMoR2Z6r5pmGl7dfNFqEvqOyqbf1ta4lknK4gc5PJn3mfA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.0.tgz", + "integrity": "sha512-t+8LsRMMDE74c6sV7KShIw13sqbqd58tlqNrsWoWBTIMw7SVQ0cZ905wLNS/FBCy/3PyooRHLFFlfrUNyyz5lA==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-create-class-features-plugin": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-class-static-block": "^7.14.5" } }, + "@babel/plugin-proposal-decorators": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.17.12.tgz", + "integrity": "sha512-gL0qSSeIk/VRfTDgtQg/EtejENssN/r3p5gJsPie1UacwiHibprpr19Z0pcK3XKuqQvjGVxsQ37Tl1MGfXzonA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/plugin-syntax-decorators": "^7.17.12", + "charcodes": "^0.2.0" + } + }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.0.tgz", - "integrity": "sha512-QGSA6ExWk95jFQgwz5GQ2Dr95cf7eI7TKutIXXTb7B1gCLTCz5hTjFTQGfLFBBiC5WSNi7udNwWsqbbMh1c4yQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" } }, + "@babel/plugin-proposal-export-default-from": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.17.12.tgz", + "integrity": "sha512-LpsTRw725eBAXXKUOnJJct+SEaOzwR78zahcLuripD2+dKc2Sj+8Q2DzA+GC/jOpOu/KlDXuxrzG214o1zTauQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-export-default-from": "^7.16.7" + } + }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.0.tgz", - "integrity": "sha512-CjI4nxM/D+5wCnhD11MHB1AwRSAYeDT+h8gCdcVJZ/OK7+wRzFsf7PFPWVpVpNRkHMmMkQWAHpTq+15IXQ1diA==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz", + "integrity": "sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.0.tgz", - "integrity": "sha512-kouIPuiv8mSi5JkEhzApg5Gn6hFyKPnlkO0a9YSzqRurH8wYzSlf6RJdzluAsbqecdW5pBvDJDfyDIUR/vLxvg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.17.12.tgz", + "integrity": "sha512-rKJ+rKBoXwLnIn7n6o6fulViHMrOThz99ybH+hKHcOZbnN14VuMnH9fo2eHE69C8pO4uX1Q7t2HYYIDmv8VYkg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-json-strings": "^7.8.3" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.0.tgz", - "integrity": "sha512-pbW0fE30sVTYXXm9lpVQQ/Vc+iTeQKiXlaNRZPPN2A2VdlWyAtsUrsQ3xydSlDW00TFMK7a8m3cDTkBF5WnV3Q==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.17.12.tgz", + "integrity": "sha512-EqFo2s1Z5yy+JeJu7SFfbIUtToJTVlC61/C7WLKDntSw4Sz6JNAIfL7zQ74VvirxpjB5kz/kIx0gCcb+5OEo2Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.0.tgz", - "integrity": "sha512-3bnHA8CAFm7cG93v8loghDYyQ8r97Qydf63BeYiGgYbjKKB/XP53W15wfRC7dvKfoiJ34f6Rbyyx2btExc8XsQ==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz", + "integrity": "sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.0.tgz", - "integrity": "sha512-FAhE2I6mjispy+vwwd6xWPyEx3NYFS13pikDBWUAFGZvq6POGs5eNchw8+1CYoEgBl9n11I3NkzD7ghn25PQ9Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.0.tgz", - "integrity": "sha512-LU/+jp89efe5HuWJLmMmFG0+xbz+I2rSI7iLc1AlaeSMDMOGzWlc5yJrMN1d04osXN4sSfpo4O+azkBNBes0jg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.0.tgz", + "integrity": "sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw==", "dev": true, "requires": { - "@babel/compat-data": "^7.16.0", - "@babel/helper-compilation-targets": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.16.0" + "@babel/plugin-transform-parameters": "^7.17.12" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.0.tgz", - "integrity": "sha512-kicDo0A/5J0nrsCPbn89mTG3Bm4XgYi0CZtvex9Oyw7gGZE3HXGD0zpQNH+mo+tEfbo8wbmMvJftOwpmPy7aVw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", + "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.0.tgz", - "integrity": "sha512-Y4rFpkZODfHrVo70Uaj6cC1JJOt3Pp0MdWSwIKtb8z1/lsjl9AmnB7ErRFV+QNGIfcY1Eruc2UMx5KaRnXjMyg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz", + "integrity": "sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", "@babel/plugin-syntax-optional-chaining": "^7.8.3" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.0.tgz", - "integrity": "sha512-IvHmcTHDFztQGnn6aWq4t12QaBXTKr1whF/dgp9kz84X6GUcwq9utj7z2wFCUfeOup/QKnOlt2k0zxkGFx9ubg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.17.12.tgz", + "integrity": "sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-proposal-private-property-in-object": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.0.tgz", - "integrity": "sha512-3jQUr/HBbMVZmi72LpjQwlZ55i1queL8KcDTQEkAHihttJnAPrcvG9ZNXIfsd2ugpizZo595egYV6xy+pv4Ofw==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.17.12.tgz", + "integrity": "sha512-/6BtVi57CJfrtDNKfK5b66ydK2J5pXUKBKSPD2G1whamMuEnZWgoOIfO8Vf9F/DoD4izBLD/Au4NMQfruzzykg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-create-class-features-plugin": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "dependencies": { "@babel/helper-annotate-as-pure": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", - "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } } } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.0.tgz", - "integrity": "sha512-ti7IdM54NXv29cA4+bNNKEMS4jLMCbJgl+Drv+FgYy0erJLAxNAIXcNjNjrRZEcWq0xJHsNVwQezskMFpF8N9g==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.17.12.tgz", + "integrity": "sha512-Wb9qLjXf3ZazqXA7IvI7ozqRIXIGPtSo+L5coFmEkhTQK18ao4UDDD0zdTGAarmbLj2urpRwrc6893cu5Bfh0A==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-syntax-async-generators": { @@ -1149,6 +1285,15 @@ "@babel/helper-plugin-utils": "^7.14.5" } }, + "@babel/plugin-syntax-decorators": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.17.12.tgz", + "integrity": "sha512-D1Hz0qtGTza8K2xGyEdVNCYLdVHukAcbQr4K3/s6r/esadyEriZovpJimQOpu8ju4/jV8dW/1xdaE0UpDroidw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, "@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", @@ -1158,6 +1303,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-export-default-from": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.16.7.tgz", + "integrity": "sha512-4C3E4NsrLOgftKaTYTULhHsuQrGv3FHrBzOMDiS7UYKIpgGBkAdawg4h+EI8zPeK9M0fiIIh72hIwsI24K7MbA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, "@babel/plugin-syntax-export-namespace-from": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", @@ -1167,6 +1321,15 @@ "@babel/helper-plugin-utils": "^7.8.3" } }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.17.12.tgz", + "integrity": "sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, "@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", @@ -1176,6 +1339,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-jsx": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.17.12.tgz", + "integrity": "sha512-spyY3E3AURfxh/RHtjx5j6hs8am5NbUBGfcZ2vB3uShSpZdQyXSf5rR5Mk76vbtlAZOelyVQ71Fg0x9SG4fsog==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, "@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", @@ -1248,13 +1420,22 @@ "@babel/helper-plugin-utils": "^7.14.5" } }, + "@babel/plugin-syntax-typescript": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz", + "integrity": "sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12" + } + }, "@babel/plugin-transform-arrow-functions": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.0.tgz", - "integrity": "sha512-vIFb5250Rbh7roWARvCLvIJ/PtAU5Lhv7BtZ1u24COwpI9Ypjsh+bZcKk6rlIyalK+r0jOc1XQ8I4ovNxNrWrA==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz", + "integrity": "sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-async-to-generator": { @@ -1269,241 +1450,308 @@ } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.0.tgz", - "integrity": "sha512-V14As3haUOP4ZWrLJ3VVx5rCnrYhMSHN/jX7z6FAt5hjRkLsb0snPCmJwSOML5oxkKO4FNoNv7V5hw/y2bjuvg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", + "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.0.tgz", - "integrity": "sha512-27n3l67/R3UrXfizlvHGuTwsRIFyce3D/6a37GRxn28iyTPvNXaW4XvznexRh1zUNLPjbLL22Id0XQElV94ruw==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.17.12.tgz", + "integrity": "sha512-jw8XW/B1i7Lqwqj2CbrViPcZijSxfguBWZP2aN59NHgxUyO/OcO1mfdCxH13QhN5LbWhPkX+f+brKGhZTiqtZQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-classes": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.0.tgz", - "integrity": "sha512-HUxMvy6GtAdd+GKBNYDWCIA776byUQH8zjnfjxwT1P1ARv/wFu8eBDpmXQcLS/IwRtrxIReGiplOwMeyO7nsDQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.17.12.tgz", + "integrity": "sha512-cvO7lc7pZat6BsvH6l/EGaI8zpl8paICaoGk+7x7guvtfak/TbIf66nYmJOH13EuG0H+Xx3M+9LQDtSvZFKXKw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", "globals": "^11.1.0" }, "dependencies": { "@babel/helper-annotate-as-pure": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", - "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } } } }, "@babel/plugin-transform-computed-properties": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.0.tgz", - "integrity": "sha512-63l1dRXday6S8V3WFY5mXJwcRAnPYxvFfTlt67bwV1rTyVTM5zrp0DBBb13Kl7+ehkCVwIZPumPpFP/4u70+Tw==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz", + "integrity": "sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-destructuring": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.0.tgz", - "integrity": "sha512-Q7tBUwjxLTsHEoqktemHBMtb3NYwyJPTJdM+wDwb0g8PZ3kQUIzNvwD5lPaqW/p54TXBc/MXZu9Jr7tbUEUM8Q==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.0.tgz", + "integrity": "sha512-Mo69klS79z6KEfrLg/1WkmVnB8javh75HX4pi2btjvlIoasuxilEyjtsQW6XPrubNd7AQy0MMaNIaQE4e7+PQw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.0.tgz", - "integrity": "sha512-FXlDZfQeLILfJlC6I1qyEwcHK5UpRCFkaoVyA1nk9A1L1Yu583YO4un2KsLBsu3IJb4CUbctZks8tD9xPQubLw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", + "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.0.tgz", - "integrity": "sha512-LIe2kcHKAZOJDNxujvmp6z3mfN6V9lJxubU4fJIGoQCkKe3Ec2OcbdlYP+vW++4MpxwG0d1wSDOJtQW5kLnkZQ==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.17.12.tgz", + "integrity": "sha512-EA5eYFUG6xeerdabina/xIoB95jJ17mAkR8ivx6ZSu9frKShBjpOGZPn511MTDTkiCO+zXnzNczvUM69YSf3Zw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.0.tgz", - "integrity": "sha512-OwYEvzFI38hXklsrbNivzpO3fh87skzx8Pnqi4LoSYeav0xHlueSoCJrSgTPfnbyzopo5b3YVAJkFIcUpK2wsw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", + "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-for-of": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.0.tgz", - "integrity": "sha512-5QKUw2kO+GVmKr2wMYSATCTTnHyscl6sxFRAY+rvN7h7WB0lcG0o4NoV6ZQU32OZGVsYUsfLGgPQpDFdkfjlJQ==", + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.1.tgz", + "integrity": "sha512-+TTB5XwvJ5hZbO8xvl2H4XaMDOAK57zF4miuC9qQJgysPNEAZZ9Z69rdF5LJkozGdZrjBIUAIyKUWRMmebI7vg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.0.tgz", - "integrity": "sha512-lBzMle9jcOXtSOXUpc7tvvTpENu/NuekNJVova5lCCWCV9/U1ho2HH2y0p6mBg8fPm/syEAbfaaemYGOHCY3mg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", + "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-literals": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.0.tgz", - "integrity": "sha512-gQDlsSF1iv9RU04clgXqRjrPyyoJMTclFt3K1cjLmTKikc0s/6vE3hlDeEVC71wLTRu72Fq7650kABrdTc2wMQ==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz", + "integrity": "sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.0.tgz", - "integrity": "sha512-WRpw5HL4Jhnxw8QARzRvwojp9MIE7Tdk3ez6vRyUk1MwgjJN0aNpRoXainLR5SgxmoXx/vsXGZ6OthP6t/RbUg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", + "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.0.tgz", - "integrity": "sha512-rWFhWbCJ9Wdmzln1NmSCqn7P0RAD+ogXG/bd9Kg5c7PKWkJtkiXmYsMBeXjDlzHpVTJ4I/hnjs45zX4dEv81xw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.0.tgz", + "integrity": "sha512-h8FjOlYmdZwl7Xm2Ug4iX2j7Qy63NANI+NQVWQzv6r25fqgg7k2dZl03p95kvqNclglHs4FZ+isv4p1uXMA+QA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.0.tgz", - "integrity": "sha512-Dzi+NWqyEotgzk/sb7kgQPJQf7AJkQBWsVp1N6JWc1lBVo0vkElUnGdr1PzUBmfsCCN5OOFya3RtpeHk15oLKQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.0.tgz", + "integrity": "sha512-cCeR0VZWtfxWS4YueAK2qtHtBPJRSaJcMlbS8jhSIm/A3E2Kpro4W1Dn4cqJtp59dtWfXjQwK7SPKF8ghs7rlw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-simple-access": "^7.16.0", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-simple-access": "^7.17.7", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.0.tgz", - "integrity": "sha512-yuGBaHS3lF1m/5R+6fjIke64ii5luRUg97N2wr+z1sF0V+sNSXPxXDdEEL/iYLszsN5VKxVB1IPfEqhzVpiqvg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.0.tgz", + "integrity": "sha512-vwKpxdHnlM5tIrRt/eA0bzfbi7gUBLN08vLu38np1nZevlPySRe6yvuATJB5F/WPJ+ur4OXwpVYq9+BsxqAQuQ==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-module-transforms": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-identifier": "^7.16.7", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.0.tgz", - "integrity": "sha512-nx4f6no57himWiHhxDM5pjwhae5vLpTK2zCnDH8+wNLJy0TVER/LJRHl2bkt6w9Aad2sPD5iNNoUpY3X9sTGDg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.0.tgz", + "integrity": "sha512-d/zZ8I3BWli1tmROLxXLc9A6YXvGK8egMxHp+E/rRwMh1Kip0AP77VwZae3snEJ33iiWwvNv2+UIIhfalqhzZA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-module-transforms": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.0.tgz", - "integrity": "sha512-LogN88uO+7EhxWc8WZuQ8vxdSyVGxhkh8WTC3tzlT8LccMuQdA81e9SGV6zY7kY2LjDhhDOFdQVxdGwPyBCnvg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz", + "integrity": "sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.0" + "@babel/helper-create-regexp-features-plugin": "^7.17.12", + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-new-target": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.0.tgz", - "integrity": "sha512-fhjrDEYv2DBsGN/P6rlqakwRwIp7rBGLPbrKxwh7oVt5NNkIhZVOY2GRV+ULLsQri1bDqwDWnU3vhlmx5B2aCw==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.17.12.tgz", + "integrity": "sha512-CaOtzk2fDYisbjAD4Sd1MTKGVIpRtx9bWLyj24Y/k6p4s4gQ3CqDGJauFJxt8M/LEx003d0i3klVqnN73qvK3w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-object-super": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.0.tgz", - "integrity": "sha512-fds+puedQHn4cPLshoHcR1DTMN0q1V9ou0mUjm8whx9pGcNvDrVVrgw+KJzzCaiTdaYhldtrUps8DWVMgrSEyg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", + "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.16.0" + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7" } }, "@babel/plugin-transform-parameters": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.3.tgz", - "integrity": "sha512-3MaDpJrOXT1MZ/WCmkOFo7EtmVVC8H4EUZVrHvFOsmwkk4lOjQj8rzv8JKUZV4YoQKeoIgk07GO+acPU9IMu/w==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz", + "integrity": "sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-property-literals": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.0.tgz", - "integrity": "sha512-XLldD4V8+pOqX2hwfWhgwXzGdnDOThxaNTgqagOcpBgIxbUvpgU2FMvo5E1RyHbk756WYgdbS0T8y0Cj9FKkWQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", + "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz", + "integrity": "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.12.tgz", + "integrity": "sha512-Lcaw8bxd1DKht3thfD4A12dqo1X16he1Lm8rIv8sTwjAYNInRS1qHa9aJoqvzpscItXvftKDCfaEQzwoVyXpEQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-jsx": "^7.17.12", + "@babel/types": "^7.17.12" + }, + "dependencies": { + "@babel/helper-annotate-as-pure": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + } + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz", + "integrity": "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==", + "dev": true, + "requires": { + "@babel/plugin-transform-react-jsx": "^7.16.7" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.0.tgz", + "integrity": "sha512-6+0IK6ouvqDn9bmEG7mEyF/pwlJXVj5lwydybpyyH3D0A7Hftk+NCTdYjnLNZksn261xaOV5ksmp20pQEmc2RQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12" + }, + "dependencies": { + "@babel/helper-annotate-as-pure": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + } } }, "@babel/plugin-transform-regenerator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.0.tgz", - "integrity": "sha512-JAvGxgKuwS2PihiSFaDrp94XOzzTUeDeOQlcKzVAyaPap7BnZXK/lvMDiubkPTdotPKOIZq9xWXWnggUMYiExg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.0.tgz", + "integrity": "sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw==", "dev": true, "requires": { - "regenerator-transform": "^0.14.2" + "@babel/helper-plugin-utils": "^7.17.12", + "regenerator-transform": "^0.15.0" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.0.tgz", - "integrity": "sha512-Dgs8NNCehHSvXdhEhln8u/TtJxfVwGYCgP2OOr5Z3Ar+B+zXicEOKNTyc+eca2cuEOMtjW6m9P9ijOt8QdqWkg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.17.12.tgz", + "integrity": "sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-runtime": { @@ -1529,68 +1777,79 @@ } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.0.tgz", - "integrity": "sha512-iVb1mTcD8fuhSv3k99+5tlXu5N0v8/DPm2mO3WACLG6al1CGZH7v09HJyUb1TtYl/Z+KrM6pHSIJdZxP5A+xow==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", + "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-spread": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.0.tgz", - "integrity": "sha512-Ao4MSYRaLAQczZVp9/7E7QHsCuK92yHRrmVNRe/SlEJjhzivq0BSn8mEraimL8wizHZ3fuaHxKH0iwzI13GyGg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz", + "integrity": "sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.17.12", "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.0.tgz", - "integrity": "sha512-/ntT2NljR9foobKk4E/YyOSwcGUXtYWv5tinMK/3RkypyNBNdhHUaq6Orw5DWq9ZcNlS03BIlEALFeQgeVAo4Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", + "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-template-literals": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.0.tgz", - "integrity": "sha512-Rd4Ic89hA/f7xUSJQk5PnC+4so50vBoBfxjdQAdvngwidM8jYIBVxBZ/sARxD4e0yMXRbJVDrYf7dyRtIIKT6Q==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.17.12.tgz", + "integrity": "sha512-kAKJ7DX1dSRa2s7WN1xUAuaQmkTpN+uig4wCKWivVXIObqGbVTUlSavHyfI2iZvz89GFAMGm9p2DBJ4Y1Tp0hw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.0.tgz", - "integrity": "sha512-++V2L8Bdf4vcaHi2raILnptTBjGEFxn5315YU+e8+EqXIucA+q349qWngCLpUYqqv233suJ6NOienIVUpS9cqg==", + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.17.12.tgz", + "integrity": "sha512-Q8y+Jp7ZdtSPXCThB6zjQ74N3lj0f6TDh1Hnf5B+sYlzQ8i5Pjp8gW0My79iekSpT4WnI06blqP6DT0OmaXXmw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.17.12" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.1.tgz", + "integrity": "sha512-F+RJmL479HJmC0KeqqwEGZMg1P7kWArLGbAKfEi9yPthJyMNjF+DjxFF/halfQvq1Q9GFM4TUbYDNV8xe4Ctqg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.0", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/plugin-syntax-typescript": "^7.17.12" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.0.tgz", - "integrity": "sha512-VFi4dhgJM7Bpk8lRc5CMaRGlKZ29W9C3geZjt9beuzSUrlJxsNwX7ReLwaL6WEvsOf2EQkyIJEPtF8EXjB/g2A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", + "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.0.tgz", - "integrity": "sha512-jHLK4LxhHjvCeZDWyA9c+P9XH1sOxRd1RO9xMtDVRAOND/PczPqizEtVdx4TQF/wyPaewqpT+tgQFYMnN/P94A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", + "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/preset-env": { @@ -1695,6 +1954,116 @@ "esutils": "^2.0.2" } }, + "@babel/preset-react": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.17.12.tgz", + "integrity": "sha512-h5U+rwreXtZaRBEQhW1hOJLMq8XNJBQ/9oymXiCXTuT/0uOwpbT0gUt+sXeOqoXBgNuUKI7TaObVwoEyWkpFgA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-transform-react-display-name": "^7.16.7", + "@babel/plugin-transform-react-jsx": "^7.17.12", + "@babel/plugin-transform-react-jsx-development": "^7.16.7", + "@babel/plugin-transform-react-pure-annotations": "^7.16.7" + } + }, + "@babel/preset-typescript": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.17.12.tgz", + "integrity": "sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-transform-typescript": "^7.17.12" + } + }, + "@babel/register": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.17.7.tgz", + "integrity": "sha512-fg56SwvXRifootQEDQAu1mKdjh5uthPzdO0N6t358FktfL4XjAVXuH58ULoiW8mesxiOgNIrxiImqEwv0+hRRA==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.5", + "source-map-support": "^0.5.16" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "@babel/runtime": { "version": "7.14.8", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz", @@ -1716,1003 +2085,1750 @@ } }, "@babel/traverse": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz", - "integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.0", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.3", - "@babel/types": "^7.16.0", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.0.tgz", + "integrity": "sha512-oNOO4vaoIQoGjDQ84LgtF/IAlxlyqL4TUuoQ7xLkQETFaHkY1F7yazhB4Kt3VcZGL0ZF/jhrEpnXqUb0M7V3sw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.18.0", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.18.0", + "@babel/types": "^7.18.0", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { "@babel/generator": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz", - "integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.0.tgz", + "integrity": "sha512-81YO9gGx6voPXlvYdZBliFXAZU8vZ9AZ6z+CjlmcnaeOcYSFbMTpdeDUO9xD9dh/68Vq03I8ZspfUTPfitcDHg==", "dev": true, "requires": { - "@babel/types": "^7.16.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.18.0", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, "@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.0.tgz", + "integrity": "sha512-vhAmLPAiC8j9K2GnsnLPCIH5wCrPpYIVBCWRBFDCB7Y/BXLqi/O+1RSTTM2bsmg6U/551+FCf9PNPxjABmxHTw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" } }, - "@csstools/convert-colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", - "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", - "dev": true + "@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } }, - "@discoveryjs/json-ext": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", - "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "@compodoc/compodoc": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@compodoc/compodoc/-/compodoc-1.1.19.tgz", + "integrity": "sha512-09vdSIgoAXWD1MiLZNhiljLNQ1XzHw/w5shw5IPcUImr/I+1Y52srUL46mEXN8AXo0hbHb5LZcgs70mmrOvY7Q==", "dev": true, "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" + "@angular-devkit/schematics": "^13.2.4", + "@babel/core": "^7.17.5", + "@babel/preset-env": "^7.16.11", + "@compodoc/live-server": "^1.2.3", + "@compodoc/ngd-transformer": "^2.1.0", + "chalk": "4.1.2", + "cheerio": "^1.0.0-rc.10", + "chokidar": "^3.5.3", + "colors": "1.4.0", + "commander": "^9.0.0", + "cosmiconfig": "^7.0.1", + "decache": "^4.6.1", + "fancy-log": "^2.0.0", + "findit2": "^2.2.3", + "fs-extra": "^10.0.1", + "glob": "^7.2.0", + "handlebars": "^4.7.7", + "html-entities": "^2.3.2", + "i18next": "^21.6.11", + "inside": "^1.0.0", + "json5": "^2.2.0", + "lodash": "^4.17.21", + "loglevel": "^1.8.0", + "loglevel-plugin-prefix": "^0.8.4", + "lunr": "^2.3.9", + "marked": "^4.0.12", + "minimist": "^1.2.5", + "opencollective-postinstall": "^2.0.3", + "os-name": "4.0.1", + "pdfjs-dist": "^2.12.313", + "pdfmake": "^0.2.4", + "semver": "^7.3.5", + "traverse": "^0.6.6", + "ts-morph": "^13.0.3", + "uuid": "^8.3.2" }, "dependencies": { - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "@angular-devkit/core": { + "version": "13.3.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.6.tgz", + "integrity": "sha512-ZmD586B+RnM2CG5+jbXh2NVfIydTc/yKSjppYDDOv4I530YBm6vpfZMwClpiNk6XLbMv7KqX4Tlr4wfxlPYYbA==", "dev": true, "requires": { - "type-fest": "^0.20.2" + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" } }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jridgewell/resolve-uri": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-1.0.0.tgz", - "integrity": "sha512-9oLAnygRMi8Q5QkYEU4XWK04B+nuoXoxjRvRxgjuChkLZFBja0YPSgdZ7dZtwhncLBcQe/I/E+fLuk5qxcYVJA==", - "dev": true - }, - "@jsdevtools/coverage-istanbul-loader": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz", - "integrity": "sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==", - "dev": true, - "requires": { - "convert-source-map": "^1.7.0", - "istanbul-lib-instrument": "^4.0.3", - "loader-utils": "^2.0.0", - "merge-source-map": "^1.1.0", - "schema-utils": "^2.7.0" - }, - "dependencies": { - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "@angular-devkit/schematics": { + "version": "13.3.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.6.tgz", + "integrity": "sha512-yLh5xc92C/FiaAp27coPiKWpSUmwoXF7vMxbJYJTyOXlt0mUITAEAwtrZQNr4yAxW/yvgTdyg7PhXaveQNTUuQ==", "dev": true, "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" + "@angular-devkit/core": "13.3.6", + "jsonc-parser": "3.0.0", + "magic-string": "0.25.7", + "ora": "5.4.1", + "rxjs": "6.6.7" } - } - } - }, - "@ngrx/effects": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-12.5.1.tgz", - "integrity": "sha512-fVNGIIntYLRWW1XWe0os2XOv03L22S4WTkX0OPZ9O6ztwuaNq0yzxWN7UeAC6H385F+g0k76KwRV78zHyP0bfQ==", - "requires": { - "tslib": "^2.0.0" + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz", + "integrity": "sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-remap-async-to-generator": "^7.16.8", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.17.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.17.12.tgz", + "integrity": "sha512-J8dbrWIOO3orDzir57NRsjg4uxucvhby0L/KZuGsWDj0g7twWK3g7JhJhOrXtuXiw8MeiSdJ3E0OW9H8LYEzLQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-remap-async-to-generator": "^7.16.8" + } + }, + "@babel/preset-env": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.0.tgz", + "integrity": "sha512-cP74OMs7ECLPeG1reiCQ/D/ypyOxgfm8uR6HRYV23vTJ7Lu1nbgj9DQDo/vH59gnn7GOAwtTDPPYV4aXzsMKHA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", + "@babel/helper-plugin-utils": "^7.17.12", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.17.12", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.17.12", + "@babel/plugin-proposal-async-generator-functions": "^7.17.12", + "@babel/plugin-proposal-class-properties": "^7.17.12", + "@babel/plugin-proposal-class-static-block": "^7.18.0", + "@babel/plugin-proposal-dynamic-import": "^7.16.7", + "@babel/plugin-proposal-export-namespace-from": "^7.17.12", + "@babel/plugin-proposal-json-strings": "^7.17.12", + "@babel/plugin-proposal-logical-assignment-operators": "^7.17.12", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.17.12", + "@babel/plugin-proposal-numeric-separator": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.18.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", + "@babel/plugin-proposal-optional-chaining": "^7.17.12", + "@babel/plugin-proposal-private-methods": "^7.17.12", + "@babel/plugin-proposal-private-property-in-object": "^7.17.12", + "@babel/plugin-proposal-unicode-property-regex": "^7.17.12", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.17.12", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.17.12", + "@babel/plugin-transform-async-to-generator": "^7.17.12", + "@babel/plugin-transform-block-scoped-functions": "^7.16.7", + "@babel/plugin-transform-block-scoping": "^7.17.12", + "@babel/plugin-transform-classes": "^7.17.12", + "@babel/plugin-transform-computed-properties": "^7.17.12", + "@babel/plugin-transform-destructuring": "^7.18.0", + "@babel/plugin-transform-dotall-regex": "^7.16.7", + "@babel/plugin-transform-duplicate-keys": "^7.17.12", + "@babel/plugin-transform-exponentiation-operator": "^7.16.7", + "@babel/plugin-transform-for-of": "^7.17.12", + "@babel/plugin-transform-function-name": "^7.16.7", + "@babel/plugin-transform-literals": "^7.17.12", + "@babel/plugin-transform-member-expression-literals": "^7.16.7", + "@babel/plugin-transform-modules-amd": "^7.18.0", + "@babel/plugin-transform-modules-commonjs": "^7.18.0", + "@babel/plugin-transform-modules-systemjs": "^7.18.0", + "@babel/plugin-transform-modules-umd": "^7.18.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.17.12", + "@babel/plugin-transform-new-target": "^7.17.12", + "@babel/plugin-transform-object-super": "^7.16.7", + "@babel/plugin-transform-parameters": "^7.17.12", + "@babel/plugin-transform-property-literals": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.18.0", + "@babel/plugin-transform-reserved-words": "^7.17.12", + "@babel/plugin-transform-shorthand-properties": "^7.16.7", + "@babel/plugin-transform-spread": "^7.17.12", + "@babel/plugin-transform-sticky-regex": "^7.16.7", + "@babel/plugin-transform-template-literals": "^7.17.12", + "@babel/plugin-transform-typeof-symbol": "^7.17.12", + "@babel/plugin-transform-unicode-escapes": "^7.16.7", + "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.0", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "ajv": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", + "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.3.1", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", + "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.21.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "@ngrx/store": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-12.5.1.tgz", - "integrity": "sha512-NLVkHLVeZc7IboXSDZlFoq1QrupmwYTYKRHS6se7ZasAv/lrIjHWsVVdICKSVRBsHZYu3+dmCXmu+YgulP7iHw==", + "@compodoc/live-server": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@compodoc/live-server/-/live-server-1.2.3.tgz", + "integrity": "sha512-hDmntVCyjjaxuJzPzBx68orNZ7TW4BtHWMnXlIVn5dqhK7vuFF/11hspO1cMmc+2QTYgqde1TBcb3127S7Zrow==", + "dev": true, "requires": { - "tslib": "^2.0.0" + "chokidar": "^3.5.2", + "colors": "1.4.0", + "connect": "^3.7.0", + "cors": "^2.8.5", + "event-stream": "4.0.1", + "faye-websocket": "0.11.x", + "http-auth": "4.1.9", + "http-auth-connect": "^1.0.5", + "morgan": "^1.10.0", + "object-assign": "^4.1.1", + "open": "8.4.0", + "proxy-middleware": "^0.15.0", + "send": "^0.18.0", + "serve-index": "^1.9.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + } + } } }, - "@ngtools/webpack": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.2.13.tgz", - "integrity": "sha512-krAwMyRqOaC1S0+IxAFid9F6A6ABip2DJ0tgCx26X+1Vw/d1GKtV9ZqDJFizMf5k1ywl9aBlhOazWpq5d6i+gw==", - "dev": true + "@compodoc/ngd-core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@compodoc/ngd-core/-/ngd-core-2.1.0.tgz", + "integrity": "sha512-nyBH7J7SJJ2AV6OeZhJ02kRtVB7ALnZJKgShjoL9CNmOFEj8AkdhP9qTBIgjaDrbsW5pF4nx32KQL2fT7RFnqw==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1", + "fancy-log": "^1.3.3", + "typescript": "^4.0.3" + }, + "dependencies": { + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + } + } }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "@compodoc/ngd-transformer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@compodoc/ngd-transformer/-/ngd-transformer-2.1.0.tgz", + "integrity": "sha512-Jo4VCMzIUtgIAdRmhHhOoRRE01gCjc5CyrUERRx0VgEzkkCm1Wmu/XHSsQP6tSpCYHBjERghqaDqH5DabkR2oQ==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@aduh95/viz.js": "^3.1.0", + "@compodoc/ngd-core": "~2.1.0", + "dot": "^1.1.3", + "fs-extra": "^9.0.1" + }, + "dependencies": { + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + } } }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "@csstools/convert-colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", + "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", "dev": true }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "@design-systems/utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@design-systems/utils/-/utils-2.12.0.tgz", + "integrity": "sha512-Y/d2Zzr+JJfN6u1gbuBUb1ufBuLMJJRZQk+dRmw8GaTpqKx5uf7cGUYGTwN02dIb3I+Tf+cW8jcGBTRiFxdYFg==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@babel/runtime": "^7.11.2", + "clsx": "^1.0.4", + "focus-lock": "^0.8.0", + "react-merge-refs": "^1.0.0" } }, - "@npmcli/git": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-2.1.0.tgz", - "integrity": "sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw==", + "@devtools-ds/object-inspector": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@devtools-ds/object-inspector/-/object-inspector-1.2.0.tgz", + "integrity": "sha512-VztcwqVwScSvYdvJVZBJYsVO/2Pew3JPpFV3T9fuCHQLlHcLYOV3aU/kBS2ScuE2O1JN0ZbobLqFLa3vQF54Fw==", "dev": true, "requires": { - "@npmcli/promise-spawn": "^1.3.2", - "lru-cache": "^6.0.0", - "mkdirp": "^1.0.4", - "npm-pick-manifest": "^6.1.1", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^2.0.2" + "@babel/runtime": "7.7.2", + "@devtools-ds/object-parser": "^1.2.0", + "@devtools-ds/themes": "^1.2.0", + "@devtools-ds/tree": "^1.2.0", + "clsx": "1.1.0" }, "dependencies": { - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "@babel/runtime": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz", + "integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==", "dev": true, "requires": { - "isexe": "^2.0.0" + "regenerator-runtime": "^0.13.2" } } } }, - "@npmcli/installed-package-contents": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", - "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", + "@devtools-ds/object-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@devtools-ds/object-parser/-/object-parser-1.2.0.tgz", + "integrity": "sha512-SjGGyiFFY8dtUpiWXAvRSzRT+hE11EAAysrq2PsC/GVLf2ZLyT2nHlQO5kDStywyTz+fjw7S7pyDRj1HG9YTTA==", "dev": true, "requires": { - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" + "@babel/runtime": "~7.5.4" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz", + "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + } } }, - "@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "@devtools-ds/themes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@devtools-ds/themes/-/themes-1.2.0.tgz", + "integrity": "sha512-LimEITorE6yWZWWuMc6OiBfLQgPrQqWbyMEmfRUDPa3PHXoAY4SpDxczfg31fgyRDUNWnZhjaJH5bBbu8VEbIw==", "dev": true, "requires": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "@babel/runtime": "~7.5.4", + "@design-systems/utils": "2.12.0", + "clsx": "1.1.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz", + "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + } } }, - "@npmcli/node-gyp": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz", - "integrity": "sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA==", - "dev": true - }, - "@npmcli/promise-spawn": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz", - "integrity": "sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg==", + "@devtools-ds/tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@devtools-ds/tree/-/tree-1.2.0.tgz", + "integrity": "sha512-hC4g4ocuo2eg7jsnzKdauxH0sDQiPW3KSM2+uK3kRgcmr9PzpBD5Kob+Y/WFSVKswFleftOGKL4BQLuRv0sPxA==", "dev": true, "requires": { - "infer-owner": "^1.0.4" + "@babel/runtime": "7.7.2", + "@devtools-ds/themes": "^1.2.0", + "clsx": "1.1.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz", + "integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + } } }, - "@npmcli/run-script": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.6.tgz", - "integrity": "sha512-e42bVZnC6VluBZBAFEr3YrdqSspG3bgilyg4nSLBJ7TRGNCzxHa92XAHxQBLYg0BmgwO4b2mf3h/l5EkEWRn3g==", + "@discoveryjs/json-ext": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", + "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, "requires": { - "@npmcli/node-gyp": "^1.0.2", - "@npmcli/promise-spawn": "^1.3.2", - "node-gyp": "^7.1.0", - "read-package-json-fast": "^2.0.1" + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } } }, - "@schematics/angular": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-12.2.13.tgz", - "integrity": "sha512-TrigQ9TCmAedf1J5PSSSfTC+sScYrITeAUN8a9rlkjZNvff8hHVyQaiZmhqL+egKQL828mhkqpnFUDd4QsPBIw==", + "@foliojs-fork/fontkit": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@foliojs-fork/fontkit/-/fontkit-1.9.1.tgz", + "integrity": "sha512-U589voc2/ROnvx1CyH9aNzOQWJp127JGU1QAylXGQ7LoEAF6hMmahZLQ4eqAcgHUw+uyW4PjtCItq9qudPkK3A==", "dev": true, "requires": { - "@angular-devkit/core": "12.2.13", - "@angular-devkit/schematics": "12.2.13", - "jsonc-parser": "3.0.0" + "@foliojs-fork/restructure": "^2.0.2", + "brfs": "^2.0.0", + "brotli": "^1.2.0", + "browserify-optional": "^1.0.1", + "clone": "^1.0.4", + "deep-equal": "^1.0.0", + "dfa": "^1.2.0", + "tiny-inflate": "^1.0.2", + "unicode-properties": "^1.2.2", + "unicode-trie": "^2.0.0" } }, - "@socket.io/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", - "dev": true - }, - "@stencil/core": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-1.17.4.tgz", - "integrity": "sha512-dmuNYM6fnHPvE2ptHoUBQtjcpXqrHnkDtdyUD6/JrZWcJt6jBtrykewObOxzpDCMLs+NT7668ussRagdVL03gQ==", + "@foliojs-fork/linebreak": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@foliojs-fork/linebreak/-/linebreak-1.1.1.tgz", + "integrity": "sha512-pgY/+53GqGQI+mvDiyprvPWgkTlVBS8cxqee03ejm6gKAQNsR1tCYCIvN9FHy7otZajzMqCgPOgC4cHdt4JPig==", + "dev": true, "requires": { - "typescript": "3.9.7" + "base64-js": "1.3.1", + "brfs": "^2.0.2", + "unicode-trie": "^2.0.0" }, "dependencies": { - "typescript": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", - "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==" + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true } } }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true - }, - "@types/component-emitter": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", - "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", - "dev": true - }, - "@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true - }, - "@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "dev": true - }, - "@types/eslint": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz", - "integrity": "sha512-74hbvsnc+7TEDa1z5YLSe4/q8hGYB3USNvCuzHUJrjPV6hXaq8IXcngCrHkuvFt0+8rFz7xYXrHgNayIX0UZvQ==", + "@foliojs-fork/pdfkit": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@foliojs-fork/pdfkit/-/pdfkit-0.13.0.tgz", + "integrity": "sha512-YXeG1fml9k97YNC9K8e292Pj2JzGt9uOIiBFuQFxHsdQ45BlxW+JU3RQK6JAvXU7kjhjP8rCcYvpk36JLD33sQ==", "dev": true, "requires": { - "@types/estree": "*", - "@types/json-schema": "*" + "@foliojs-fork/fontkit": "^1.9.1", + "@foliojs-fork/linebreak": "^1.1.1", + "crypto-js": "^4.0.0", + "png-js": "^1.0.0" } }, - "@types/eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", + "@foliojs-fork/restructure": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@foliojs-fork/restructure/-/restructure-2.0.2.tgz", + "integrity": "sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, "requires": { - "@types/eslint": "*", - "@types/estree": "*" + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" } }, - "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "requires": { - "@types/minimatch": "*", - "@types/node": "*" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } } }, - "@types/jasmine": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.8.2.tgz", - "integrity": "sha512-u5h7dqzy2XpXTzhOzSNQUQpKGFvROF8ElNX9P/TJvsHnTg/JvsAseVsGWQAQQldqanYaM+5kwxW909BBFAUYsg==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" - }, - "@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", - "dev": true - }, - "@types/node": { - "version": "12.20.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz", - "integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==", - "dev": true - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, - "@types/webpack-sources": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.9.tgz", - "integrity": "sha512-bvzMnzqoK16PQIC8AYHNdW45eREJQMd6WG/msQWX5V2+vZmODCOPb4TJcbgRljTZZTwTM4wUMcsI8FftNA7new==", + "@jest/transform": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", + "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", "dev": true, "requires": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.6.1" + "@babel/core": "^7.1.0", + "@jest/types": "^26.6.2", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.6.2", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.6.2", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, - "@typescript-eslint/eslint-plugin": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", - "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/experimental-utils": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", - "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", "dev": true, "requires": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", - "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "@typescript-eslint/scope-manager": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", - "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", + "@jridgewell/gen-mapping": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", + "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0" + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" } }, - "@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", + "@jridgewell/resolve-uri": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-1.0.0.tgz", + "integrity": "sha512-9oLAnygRMi8Q5QkYEU4XWK04B+nuoXoxjRvRxgjuChkLZFBja0YPSgdZ7dZtwhncLBcQe/I/E+fLuk5qxcYVJA==", "dev": true }, - "@typescript-eslint/typescript-estree": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", - "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - } + "@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true }, - "@typescript-eslint/visitor-keys": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", - "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", + "@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", + "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", "dev": true, "requires": { - "@typescript-eslint/types": "4.33.0", - "eslint-visitor-keys": "^2.0.0" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "dependencies": { + "@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true + } } }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "@jsdevtools/coverage-istanbul-loader": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz", + "integrity": "sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==", "dev": true, "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "convert-source-map": "^1.7.0", + "istanbul-lib-instrument": "^4.0.3", + "loader-utils": "^2.0.0", + "merge-source-map": "^1.1.0", + "schema-utils": "^2.7.0" + }, + "dependencies": { + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + } } }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true + "@mdx-js/mdx": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz", + "integrity": "sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==", + "dev": true, + "requires": { + "@babel/core": "7.12.9", + "@babel/plugin-syntax-jsx": "7.12.1", + "@babel/plugin-syntax-object-rest-spread": "7.8.3", + "@mdx-js/util": "1.6.22", + "babel-plugin-apply-mdx-type-prop": "1.6.22", + "babel-plugin-extract-import-names": "1.6.22", + "camelcase-css": "2.0.1", + "detab": "2.0.4", + "hast-util-raw": "6.0.1", + "lodash.uniq": "4.5.0", + "mdast-util-to-hast": "10.0.1", + "remark-footnotes": "2.0.0", + "remark-mdx": "1.6.22", + "remark-parse": "8.0.3", + "remark-squeeze-paragraphs": "4.0.0", + "style-to-object": "0.3.0", + "unified": "9.2.0", + "unist-builder": "2.0.3", + "unist-util-visit": "2.0.3" + }, + "dependencies": { + "@babel/core": { + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", + "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.7", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.9", + "@babel/types": "^7.12.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", + "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "@mdx-js/react": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz", + "integrity": "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==", "dev": true }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "@mdx-js/util": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.6.22.tgz", + "integrity": "sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==", "dev": true }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", "dev": true, "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + }, + "dependencies": { + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "dev": true + } } }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, + "@ngrx/effects": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-12.5.1.tgz", + "integrity": "sha512-fVNGIIntYLRWW1XWe0os2XOv03L22S4WTkX0OPZ9O6ztwuaNq0yzxWN7UeAC6H385F+g0k76KwRV78zHyP0bfQ==", "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "tslib": "^2.0.0" } }, - "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, + "@ngrx/store": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-12.5.1.tgz", + "integrity": "sha512-NLVkHLVeZc7IboXSDZlFoq1QrupmwYTYKRHS6se7ZasAv/lrIjHWsVVdICKSVRBsHZYu3+dmCXmu+YgulP7iHw==", "requires": { - "@xtuc/ieee754": "^1.2.0" + "tslib": "^2.0.0" } }, - "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "@ngtools/webpack": { + "version": "12.2.17", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-12.2.17.tgz", + "integrity": "sha512-uaS+2YZgPDW3VmUuwh4/yfIFV1KRVGWefc6xLWIqKRKs6mlRYs65m3ib9dX7CTS4kQMCbhxkxMbpBO2yXlzfvA==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "requires": { - "@xtuc/long": "4.2.2" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" } }, - "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" } }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "@npmcli/git": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-2.1.0.tgz", + "integrity": "sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@npmcli/promise-spawn": "^1.3.2", + "lru-cache": "^6.0.0", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^6.1.1", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "@npmcli/installed-package-contents": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", + "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" } }, - "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" } }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true - }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "@npmcli/node-gyp": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz", + "integrity": "sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA==", "dev": true }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "@npmcli/promise-spawn": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz", + "integrity": "sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg==", "dev": true, "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "infer-owner": "^1.0.4" } }, - "acorn": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", - "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==" - }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true - }, - "adjust-sourcemap-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", - "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "@npmcli/run-script": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-2.0.0.tgz", + "integrity": "sha512-fSan/Pu11xS/TdaTpTB0MRn9guwGU8dye+x56mEVgBEd/QsybBbYcAL0phPXi8SGWFEChkQd6M9qL4y6VOpFig==", "dev": true, "requires": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" + "@npmcli/node-gyp": "^1.0.2", + "@npmcli/promise-spawn": "^1.3.2", + "node-gyp": "^8.2.0", + "read-package-json-fast": "^2.0.1" } }, - "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==", + "@schematics/angular": { + "version": "12.2.17", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-12.2.17.tgz", + "integrity": "sha512-HM/4KkQu944KL5ebhIyy1Ot5OV6prHNW7kmGeMVeQefLSbbfMQCHLa1psB9UU9BoahwGhUBvleLylNSitOBCgg==", "dev": true, "requires": { - "debug": "4" + "@angular-devkit/core": "12.2.17", + "@angular-devkit/schematics": "12.2.17", + "jsonc-parser": "3.0.0" } }, - "agentkeepalive": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.4.tgz", - "integrity": "sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ==", + "@storybook/addon-actions": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.5.4.tgz", + "integrity": "sha512-O70+brZOW78LBjbKsppXy37NQj4r3b2LQh3zUkIE1n37nb0bYhewr39UlT1/VhfDD4QA6PsBOqp0Mum8/V0GgQ==", "dev": true, "requires": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/theming": "6.5.4", + "core-js": "^3.8.2", + "fast-deep-equal": "^3.1.3", + "global": "^4.4.0", + "lodash": "^4.17.21", + "polished": "^4.2.2", + "prop-types": "^15.7.2", + "react-inspector": "^5.1.0", + "regenerator-runtime": "^0.13.7", + "telejson": "^6.0.8", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2", + "uuid-browser": "^3.1.0" + } + }, + "@storybook/addon-backgrounds": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-6.5.4.tgz", + "integrity": "sha512-lajT9N7VpBQRPHJF1iAGUIxJNuMwqZcMjijVAMoWX4gOdnGvjK7MpwoYbD2xAPXYrugJUGiYRG608d4PgPZT4w==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/theming": "6.5.4", + "core-js": "^3.8.2", + "global": "^4.4.0", + "memoizerific": "^1.11.3", + "regenerator-runtime": "^0.13.7", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" } }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "@storybook/addon-controls": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-6.5.4.tgz", + "integrity": "sha512-+2s++u1yXGs94BoxMVOOa4Ld0u6ylV5KdS9cWigyQz/OP3ZeoXDx6kCaJfYhEe/7Ccn59g11XjG4W8pjHMdb4A==", "dev": true, "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/core-common": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/node-logger": "6.5.4", + "@storybook/store": "6.5.4", + "@storybook/theming": "6.5.4", + "core-js": "^3.8.2", + "lodash": "^4.17.21", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/addon-docs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-6.5.4.tgz", + "integrity": "sha512-Sqx8XJN760rbFGlNXywKG1QtG24X6gHgWXMEdfA1yy6FbH8vXu8p9VVm6KV3tHcb7o+ihSK7pObKtSXNdumD/A==", + "dev": true, + "requires": { + "@babel/plugin-transform-react-jsx": "^7.12.12", + "@babel/preset-env": "^7.12.11", + "@jest/transform": "^26.6.2", + "@mdx-js/react": "^1.6.22", + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/core-common": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/docs-tools": "6.5.4", + "@storybook/mdx1-csf": "^0.0.1-canary.1.eed86d7.0", + "@storybook/node-logger": "6.5.4", + "@storybook/postinstall": "6.5.4", + "@storybook/preview-web": "6.5.4", + "@storybook/source-loader": "6.5.4", + "@storybook/store": "6.5.4", + "@storybook/theming": "6.5.4", + "babel-loader": "^8.0.0", + "core-js": "^3.8.2", + "fast-deep-equal": "^3.1.3", + "global": "^4.4.0", + "lodash": "^4.17.21", + "regenerator-runtime": "^0.13.7", + "remark-external-links": "^8.0.0", + "remark-slug": "^6.0.0", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" } }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true - }, - "ajv-formats": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", - "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", - "dev": true, - "requires": { - "ajv": "^8.0.0" + "@storybook/addon-essentials": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-6.5.4.tgz", + "integrity": "sha512-/ayPl274Tdrfz0YG0oSovkrXJNbstFm/Zoz7KO4wNMFgxTYes3kk+ruwkFIZegunBB8yR77cSzWHKqS9pFD8tA==", + "dev": true, + "requires": { + "@storybook/addon-actions": "6.5.4", + "@storybook/addon-backgrounds": "6.5.4", + "@storybook/addon-controls": "6.5.4", + "@storybook/addon-docs": "6.5.4", + "@storybook/addon-measure": "6.5.4", + "@storybook/addon-outline": "6.5.4", + "@storybook/addon-toolbars": "6.5.4", + "@storybook/addon-viewport": "6.5.4", + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/core-common": "6.5.4", + "@storybook/node-logger": "6.5.4", + "core-js": "^3.8.2", + "regenerator-runtime": "^0.13.7", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/addon-interactions": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-6.5.4.tgz", + "integrity": "sha512-5gSpJfrrfYWY01zNCTPBTJvMdZh4q4hi9aFkB/DQPYhcLJ7EsUTGQmqEF2WBujhsD06k5Z2HjbDxKceHFgM27g==", + "dev": true, + "requires": { + "@devtools-ds/object-inspector": "^1.1.2", + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/core-common": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/instrumenter": "6.5.4", + "@storybook/theming": "6.5.4", + "core-js": "^3.8.2", + "global": "^4.4.0", + "jest-mock": "^27.0.6", + "polished": "^4.2.2", + "ts-dedent": "^2.2.0" + } + }, + "@storybook/addon-links": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-6.5.4.tgz", + "integrity": "sha512-mJcg4Gpo7P6TFdyWpE2DQcMwSjfotPST3y713Ircgc+sYOouIPRdrxGpdvhLWWncsC5wK/z2xZyYtBWPNiKN/g==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/router": "6.5.4", + "@types/qs": "^6.9.5", + "core-js": "^3.8.2", + "global": "^4.4.0", + "prop-types": "^15.7.2", + "qs": "^6.10.0", + "regenerator-runtime": "^0.13.7", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/addon-measure": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-6.5.4.tgz", + "integrity": "sha512-yucb2WnQMUyPo0cnbKKeIMp4pb18bU2zE8AFLm1GcQYJ56IfYD5EJbfpP7o3KpcMRRrZFjITggqhdU8DtncZkQ==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "core-js": "^3.8.2", + "global": "^4.4.0" + } + }, + "@storybook/addon-outline": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-6.5.4.tgz", + "integrity": "sha512-Qj9KfPTllcT0CxHqfIvKUbTxwu2ujIq/YfguFBecSQCTGwYTyTmLHzS1vwFHyEoVPgILLnQfsBLdLs57TaGl+A==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "core-js": "^3.8.2", + "global": "^4.4.0", + "regenerator-runtime": "^0.13.7", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/addon-toolbars": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-6.5.4.tgz", + "integrity": "sha512-cK80/EjDJ+Hr7/lqsEqasyZGHx5+j9pVQRWWhTEDzURcAsxh4HFSw9VSOnDk+EAgHrlvjm+PX2xeaHfT9ezo6Q==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/theming": "6.5.4", + "core-js": "^3.8.2", + "regenerator-runtime": "^0.13.7" + } + }, + "@storybook/addon-viewport": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-6.5.4.tgz", + "integrity": "sha512-K0DrUTwiGY9IeTVUgwgl0boEYzn7QbYz34N7C5CMEUFWIBNehc7FJu/Rc+d2ltNz1w3jtnQPAFpt0FQ8ZYvVGg==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/theming": "6.5.4", + "core-js": "^3.8.2", + "global": "^4.4.0", + "memoizerific": "^1.11.3", + "prop-types": "^15.7.2", + "regenerator-runtime": "^0.13.7" + } + }, + "@storybook/addons": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.4.tgz", + "integrity": "sha512-biPtPQ80HwVJeJl6ghF0yFMWZ9apuh+oxCWezeb9t0lr7EB8MLbZAGz8PbRerwDzV+mW4ZNV2RSvvnDD5MH/zg==", + "dev": true, + "requires": { + "@storybook/api": "6.5.4", + "@storybook/channels": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/router": "6.5.4", + "@storybook/theming": "6.5.4", + "@types/webpack-env": "^1.16.0", + "core-js": "^3.8.2", + "global": "^4.4.0", + "regenerator-runtime": "^0.13.7" + } + }, + "@storybook/angular": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/angular/-/angular-6.5.4.tgz", + "integrity": "sha512-nA/R+K9BYf/Lteu2Jbzww32Selw7E/yEfs0jtVLIqkLCAf84StD4s5Gnqb2hdGi8f9dI8vZVdTXWlj+ChDo9gw==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/core": "6.5.4", + "@storybook/core-common": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/docs-tools": "6.5.4", + "@storybook/node-logger": "6.5.4", + "@storybook/semver": "^7.3.2", + "@storybook/store": "6.5.4", + "@types/node": "^14.14.20 || ^16.0.0", + "@types/react": "^16.14.23", + "@types/react-dom": "^16.9.14", + "@types/webpack-env": "^1.16.0", + "autoprefixer": "^9.8.6", + "core-js": "^3.8.2", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^4.1.6", + "global": "^4.4.0", + "nanoid": "^3.1.23", + "p-limit": "^3.1.0", + "postcss": "^7.0.36", + "postcss-loader": "^4.2.0", + "raw-loader": "^4.0.2", + "react": "^16.14.0", + "react-dom": "^16.14.0", + "read-pkg-up": "^7.0.1", + "regenerator-runtime": "^0.13.7", + "sass-loader": "^10.1.0", + "telejson": "^6.0.8", + "ts-dedent": "^2.0.0", + "ts-loader": "^8.0.14", + "tsconfig-paths-webpack-plugin": "^3.3.0", + "util-deprecate": "^1.0.2", + "webpack": ">=4.0.0 <6.0.0" }, "dependencies": { - "ajv": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", - "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", + "@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "core-js": "^3.6.5", + "find-up": "^4.1.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + } } }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "@types/node": { + "version": "16.11.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz", + "integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==", "dev": true - } - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "autoprefixer": { - "version": "9.8.8", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", - "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", - "dev": true, - "requires": { - "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001109", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "picocolors": "^0.2.1", - "postcss": "^7.0.32", - "postcss-value-parser": "^4.1.0" - }, - "dependencies": { + }, + "@types/react": { + "version": "16.14.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.26.tgz", + "integrity": "sha512-c/5CYyciOO4XdFcNhZW1O2woVx86k4T+DO2RorHZL7EhitkNQgSD/SgpdZJAUJa/qjVgOmTM44gHkAdZSXeQuQ==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "dependencies": { + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + } + } + }, + "fork-ts-checker-webpack-plugin": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz", + "integrity": "sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "chalk": "^2.4.1", + "micromatch": "^3.1.10", + "minimatch": "^3.0.4", + "semver": "^5.6.0", + "tapable": "^1.0.0", + "worker-rpc": "^0.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, "picocolors": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", @@ -2729,289 +3845,6673 @@ "source-map": "^0.6.1" } }, + "postcss-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-4.3.0.tgz", + "integrity": "sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.4" + } + }, + "sass-loader": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.2.1.tgz", + "integrity": "sha512-RRvWl+3K2LSMezIsd008ErK4rk6CulIMSwrcc2aZvjymUgKo/vjXGp1rSWmfTUX7bblEOz8tst4wBwWtCGBqKA==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "neo-async": "^2.6.2", + "schema-utils": "^3.0.0", + "semver": "^7.3.2" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - } - } - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "babel-loader": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", - "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", - "dev": true, - "requires": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true }, - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } } } }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.3.tgz", - "integrity": "sha512-NDZ0auNRzmAfE1oDDPW2JhzIMXUk+FFe2ICejmt5T4ocKgiQx3e0VCRx9NCAidcMtL2RUZaWtXnmjTCkx0tcbA==", + "@storybook/api": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.5.4.tgz", + "integrity": "sha512-nZAf7/6bcOMb6yDYsCr2rWZuKtGfHQhyVvlW7uD1uPVytQ0OHS/D215C2odTNvIRxTiG67dQ4zeXcZPHHar+qQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.4", - "semver": "^6.1.1" + "@storybook/channels": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/router": "6.5.4", + "@storybook/semver": "^7.3.2", + "@storybook/theming": "6.5.4", + "core-js": "^3.8.2", + "fast-deep-equal": "^3.1.3", + "global": "^4.4.0", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "regenerator-runtime": "^0.13.7", + "store2": "^2.12.0", + "telejson": "^6.0.8", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "dev": true, + "requires": { + "core-js": "^3.6.5", + "find-up": "^4.1.0" + } } } }, - "babel-plugin-polyfill-corejs3": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.5.tgz", - "integrity": "sha512-ninF5MQNwAX9Z7c9ED+H2pGt1mXdP4TqzlHKyPIYmJIYz0N+++uwdM7RnJukklhzJ54Q84vA4ZJkgs7lu5vqcw==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.2", - "core-js-compat": "^3.16.2" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.3.tgz", - "integrity": "sha512-JVE78oRZPKFIeUqFGrSORNzQnrDwZR16oiWeGM8ZyjBn2XAT5OjP+wXx5ESuo33nUsFUEJYjtklnsKbxW5L+7g==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.4" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" + "@storybook/builder-webpack4": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack4/-/builder-webpack4-6.5.4.tgz", + "integrity": "sha512-9AaSix850yOG7deAG8PDrAVNiDcTBm422Fjo9+OkONVN8tYIObW72U9kjwSrVL2HRMwu6Ikx939z1FBgA80NSw==", + "dev": true, + "requires": { + "@babel/core": "^7.12.10", + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/channel-postmessage": "6.5.4", + "@storybook/channels": "6.5.4", + "@storybook/client-api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/core-common": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/node-logger": "6.5.4", + "@storybook/preview-web": "6.5.4", + "@storybook/router": "6.5.4", + "@storybook/semver": "^7.3.2", + "@storybook/store": "6.5.4", + "@storybook/theming": "6.5.4", + "@storybook/ui": "6.5.4", + "@types/node": "^14.0.10 || ^16.0.0", + "@types/webpack": "^4.41.26", + "autoprefixer": "^9.8.6", + "babel-loader": "^8.0.0", + "case-sensitive-paths-webpack-plugin": "^2.3.0", + "core-js": "^3.8.2", + "css-loader": "^3.6.0", + "file-loader": "^6.2.0", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^4.1.6", + "glob": "^7.1.6", + "glob-promise": "^3.4.0", + "global": "^4.4.0", + "html-webpack-plugin": "^4.0.0", + "pnp-webpack-plugin": "1.6.4", + "postcss": "^7.0.36", + "postcss-flexbugs-fixes": "^4.2.1", + "postcss-loader": "^4.2.0", + "raw-loader": "^4.0.2", + "stable": "^0.1.8", + "style-loader": "^1.3.0", + "terser-webpack-plugin": "^4.2.3", + "ts-dedent": "^2.0.0", + "url-loader": "^4.1.1", + "util-deprecate": "^1.0.2", + "webpack": "4", + "webpack-dev-middleware": "^3.7.3", + "webpack-filter-warnings-plugin": "^1.2.1", + "webpack-hot-middleware": "^2.25.1", + "webpack-virtual-modules": "^0.2.2" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "core-js": "^3.6.5", + "find-up": "^4.1.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + } } }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "@types/node": { + "version": "16.11.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz", + "integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" } }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "@xtuc/ieee754": "^1.2.0" } - } - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "@xtuc/long": "4.2.2" } - } - } - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dev": true, - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", "dev": true }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", "dev": true, "requires": { - "ms": "2.0.0" + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true - } - } - }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, - "requires": { - "array-flatten": "^2.1.0", + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "css-loader": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "enhanced-resolve": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "dependencies": { + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + } + } + }, + "fork-ts-checker-webpack-plugin": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz", + "integrity": "sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "chalk": "^2.4.1", + "micromatch": "^3.1.10", + "minimatch": "^3.0.4", + "semver": "^5.6.0", + "tapable": "^1.0.0", + "worker-rpc": "^0.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "postcss-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-4.3.0.tgz", + "integrity": "sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.4", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.4" + }, + "dependencies": { + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "dev": true, + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "style-loader": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz", + "integrity": "sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.7.0" + }, + "dependencies": { + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "terser-webpack-plugin": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", + "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", + "dev": true, + "requires": { + "cacache": "^15.0.5", + "find-cache-dir": "^3.3.1", + "jest-worker": "^26.5.0", + "p-limit": "^3.0.2", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", + "source-map": "^0.6.1", + "terser": "^5.3.4", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "watchpack": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "dev": true, + "requires": { + "chokidar": "^3.4.1", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" + } + }, + "webpack": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", + "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.5.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + } + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + } + } + } + }, + "webpack-dev-middleware": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", + "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", + "dev": true, + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@storybook/builder-webpack5": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-6.5.4.tgz", + "integrity": "sha512-BoqQkgnyqwySEcfaDreBG9SUnGTzp8jcfdUPqewtqyJK5qQgh79v2VowpSxT00G+eyWcsDkct2U4JnTmjrDpDg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.10", + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/channel-postmessage": "6.5.4", + "@storybook/channels": "6.5.4", + "@storybook/client-api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/core-common": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/node-logger": "6.5.4", + "@storybook/preview-web": "6.5.4", + "@storybook/router": "6.5.4", + "@storybook/semver": "^7.3.2", + "@storybook/store": "6.5.4", + "@storybook/theming": "6.5.4", + "@types/node": "^14.0.10 || ^16.0.0", + "babel-loader": "^8.0.0", + "babel-plugin-named-exports-order": "^0.0.2", + "browser-assert": "^1.2.1", + "case-sensitive-paths-webpack-plugin": "^2.3.0", + "core-js": "^3.8.2", + "css-loader": "^5.0.1", + "fork-ts-checker-webpack-plugin": "^6.0.4", + "glob": "^7.1.6", + "glob-promise": "^3.4.0", + "html-webpack-plugin": "^5.0.0", + "path-browserify": "^1.0.1", + "process": "^0.11.10", + "stable": "^0.1.8", + "style-loader": "^2.0.0", + "terser-webpack-plugin": "^5.0.3", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2", + "webpack": "^5.9.0", + "webpack-dev-middleware": "^4.1.0", + "webpack-hot-middleware": "^2.25.1", + "webpack-virtual-modules": "^0.4.1" + }, + "dependencies": { + "@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "dev": true, + "requires": { + "core-js": "^3.6.5", + "find-up": "^4.1.0" + } + }, + "@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true + }, + "@types/node": { + "version": "16.11.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz", + "integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==", + "dev": true + }, + "clean-css": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", + "integrity": "sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + } + }, + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + }, + "css-loader": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", + "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.15", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.5" + } + }, + "html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "requires": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + } + }, + "html-webpack-plugin": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", + "dev": true, + "requires": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + } + }, + "htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "requires": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "requires": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "style-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", + "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + } + }, + "terser": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.13.1.tgz", + "integrity": "sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA==", + "dev": true, + "requires": { + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map": "~0.8.0-beta.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "requires": { + "whatwg-url": "^7.0.0" + } + } + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "webpack-dev-middleware": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-4.3.0.tgz", + "integrity": "sha512-PjwyVY95/bhBh6VUqt6z4THplYcsvQ8YNNBTBM873xLVmw8FLeALn0qurHbs9EmcfhzQis/eoqypSnZeuUz26w==", + "dev": true, + "requires": { + "colorette": "^1.2.2", + "mem": "^8.1.1", + "memfs": "^3.2.2", + "mime-types": "^2.1.30", + "range-parser": "^1.2.1", + "schema-utils": "^3.0.0" + } + }, + "webpack-virtual-modules": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.3.tgz", + "integrity": "sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==", + "dev": true + }, + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "@storybook/channel-postmessage": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.5.4.tgz", + "integrity": "sha512-K9UG32KRXB4YKg9/WpyE6aoWBjOI2+XkFbjab9FEpI7zxJSEjKlOIqJk1uE9Xm+oWNvj80MvwD9ecGtJtw9BsA==", + "dev": true, + "requires": { + "@storybook/channels": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/core-events": "6.5.4", + "core-js": "^3.8.2", + "global": "^4.4.0", + "qs": "^6.10.0", + "telejson": "^6.0.8" + } + }, + "@storybook/channel-websocket": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/channel-websocket/-/channel-websocket-6.5.4.tgz", + "integrity": "sha512-BQtN7ZVoeUUIDtQdsnVCOzKIfpODAIW4IQ8cEuYeHiQDuGWW0+4MuVP5fzfHVugXRoQEF+OBxP8YcVVgeakyQg==", + "dev": true, + "requires": { + "@storybook/channels": "6.5.4", + "@storybook/client-logger": "6.5.4", + "core-js": "^3.8.2", + "global": "^4.4.0", + "telejson": "^6.0.8" + } + }, + "@storybook/channels": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.5.4.tgz", + "integrity": "sha512-I78N4AIipaD5mrkh5p3Yiu4iim2G3Ci7N8Y7UeRtQ4TJJzSAEuYMm7ydlpmoEM6G+4+hdU0lG2GjaeWNkIFvtA==", + "dev": true, + "requires": { + "core-js": "^3.8.2", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/client-api": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.5.4.tgz", + "integrity": "sha512-2QMcjXBOxGiqcBWqZu/v1lXSiT8jRCiHL8yiUncYK57rRwB6xltX43CcY9IZRnq2iNtQ+BfrWV6RgtbIa9mHhg==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/channel-postmessage": "6.5.4", + "@storybook/channels": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/store": "6.5.4", + "@types/qs": "^6.9.5", + "@types/webpack-env": "^1.16.0", + "core-js": "^3.8.2", + "fast-deep-equal": "^3.1.3", + "global": "^4.4.0", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "qs": "^6.10.0", + "regenerator-runtime": "^0.13.7", + "store2": "^2.12.0", + "synchronous-promise": "^2.0.15", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/client-logger": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.5.4.tgz", + "integrity": "sha512-8z5cdocpLW2bK6lTGdlBoX8njmNLTd9rrzSEIYRtobAbWjyfB6leCY6F79OVDZCXGsXIOrx/e1fwzioCT/sJSQ==", + "dev": true, + "requires": { + "core-js": "^3.8.2", + "global": "^4.4.0" + } + }, + "@storybook/components": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.5.4.tgz", + "integrity": "sha512-FCJJm2r/OzW3QvavTCdHZeTubJkbNJeoiGKeZs9kMYSgDZAr4cDUr1xUl30Hw57Uy3TCY9gnYdtGZgCP1tQiLw==", + "dev": true, + "requires": { + "@storybook/client-logger": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/theming": "6.5.4", + "@types/react-syntax-highlighter": "11.0.5", + "core-js": "^3.8.2", + "qs": "^6.10.0", + "react-syntax-highlighter": "^15.4.5", + "regenerator-runtime": "^0.13.7", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/core": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-6.5.4.tgz", + "integrity": "sha512-de43+5FgQnLWTJw51h4qpaDVy1aHFte2eE2wi5rC3VHICxqQJJQqtzDrAtZ9NoKhztS5tZHA6pk60fh1TtPk8g==", + "dev": true, + "requires": { + "@storybook/core-client": "6.5.4", + "@storybook/core-server": "6.5.4" + } + }, + "@storybook/core-client": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/core-client/-/core-client-6.5.4.tgz", + "integrity": "sha512-0rlQ5reKuCeFwqXdicPSLX/0ISAoEpVQgmBEosGt+1knRUfzS/IdLaBO+ZEwh+ajldFo3k9JfZOfxBOFqPMhsg==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/channel-postmessage": "6.5.4", + "@storybook/channel-websocket": "6.5.4", + "@storybook/client-api": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/preview-web": "6.5.4", + "@storybook/store": "6.5.4", + "@storybook/ui": "6.5.4", + "airbnb-js-shims": "^2.2.1", + "ansi-to-html": "^0.6.11", + "core-js": "^3.8.2", + "global": "^4.4.0", + "lodash": "^4.17.21", + "qs": "^6.10.0", + "regenerator-runtime": "^0.13.7", + "ts-dedent": "^2.0.0", + "unfetch": "^4.2.0", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/core-common": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-6.5.4.tgz", + "integrity": "sha512-JJ9PJr8FRUfTVC9KvkTl+K1Fut1hxMOJYuknmh8HdL8mSFzFIUzaizggEf9X7WT+0VVQkzj3Yit6s6dWA5Rh8w==", + "dev": true, + "requires": { + "@babel/core": "^7.12.10", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-decorators": "^7.12.12", + "@babel/plugin-proposal-export-default-from": "^7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.12.7", + "@babel/plugin-proposal-private-methods": "^7.12.1", + "@babel/plugin-proposal-private-property-in-object": "^7.12.1", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.12.1", + "@babel/plugin-transform-block-scoping": "^7.12.12", + "@babel/plugin-transform-classes": "^7.12.1", + "@babel/plugin-transform-destructuring": "^7.12.1", + "@babel/plugin-transform-for-of": "^7.12.1", + "@babel/plugin-transform-parameters": "^7.12.1", + "@babel/plugin-transform-shorthand-properties": "^7.12.1", + "@babel/plugin-transform-spread": "^7.12.1", + "@babel/preset-env": "^7.12.11", + "@babel/preset-react": "^7.12.10", + "@babel/preset-typescript": "^7.12.7", + "@babel/register": "^7.12.1", + "@storybook/node-logger": "6.5.4", + "@storybook/semver": "^7.3.2", + "@types/node": "^14.0.10 || ^16.0.0", + "@types/pretty-hrtime": "^1.0.0", + "babel-loader": "^8.0.0", + "babel-plugin-macros": "^3.0.1", + "babel-plugin-polyfill-corejs3": "^0.1.0", + "chalk": "^4.1.0", + "core-js": "^3.8.2", + "express": "^4.17.1", + "file-system-cache": "^1.0.5", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.0.4", + "fs-extra": "^9.0.1", + "glob": "^7.1.6", + "handlebars": "^4.7.7", + "interpret": "^2.2.0", + "json5": "^2.1.3", + "lazy-universal-dotenv": "^3.0.1", + "picomatch": "^2.3.0", + "pkg-dir": "^5.0.0", + "pretty-hrtime": "^1.0.3", + "resolve-from": "^5.0.0", + "slash": "^3.0.0", + "telejson": "^6.0.8", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2", + "webpack": "4" + }, + "dependencies": { + "@babel/helper-define-polyfill-provider": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.5.tgz", + "integrity": "sha512-nXuzCSwlJ/WKr8qxzW816gwyT6VZgiJG17zR40fou70yfAcqjoNyTLl/DQ+FExw5Hx5KNqshmN8Ldl/r2N7cTg==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "dev": true, + "requires": { + "core-js": "^3.6.5", + "find-up": "^4.1.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + } + } + }, + "@types/node": { + "version": "16.11.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz", + "integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.7.tgz", + "integrity": "sha512-u+gbS9bbPhZWEeyy1oR/YaaSpod/KDT07arZHb80aTpl8H5ZBq+uN1nN9/xtX7jQyfLdPfoqI4Rue/MQSWJquw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.1.5", + "core-js-compat": "^3.8.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "enhanced-resolve": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "dependencies": { + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + } + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "requires": { + "find-up": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + } + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "watchpack": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "dev": true, + "requires": { + "chokidar": "^3.4.1", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" + } + }, + "webpack": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", + "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.5.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@storybook/core-events": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.5.4.tgz", + "integrity": "sha512-b03mPlzfX/TkeKqi7cgFzRof80dBb98w7ui6x0GzIXXcG/O0KebzqZ41/vXQICwsbkAIp5JQdkTEq9RUOh/KCg==", + "dev": true, + "requires": { + "core-js": "^3.8.2" + } + }, + "@storybook/core-server": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-6.5.4.tgz", + "integrity": "sha512-Fmapj0xHHNQ3FUFXiHR/Zex9hBWnxKYMANxmNslAHbD3BEpf8xPYcjtptVvr0hJyHtnygtwqPbR0CUBbCG0KKQ==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.3", + "@storybook/builder-webpack4": "6.5.4", + "@storybook/core-client": "6.5.4", + "@storybook/core-common": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/csf-tools": "6.5.4", + "@storybook/manager-webpack4": "6.5.4", + "@storybook/node-logger": "6.5.4", + "@storybook/semver": "^7.3.2", + "@storybook/store": "6.5.4", + "@storybook/telemetry": "6.5.4", + "@types/node": "^14.0.10 || ^16.0.0", + "@types/node-fetch": "^2.5.7", + "@types/pretty-hrtime": "^1.0.0", + "@types/webpack": "^4.41.26", + "better-opn": "^2.1.1", + "boxen": "^5.1.2", + "chalk": "^4.1.0", + "cli-table3": "^0.6.1", + "commander": "^6.2.1", + "compression": "^1.7.4", + "core-js": "^3.8.2", + "cpy": "^8.1.2", + "detect-port": "^1.3.0", + "express": "^4.17.1", + "fs-extra": "^9.0.1", + "global": "^4.4.0", + "globby": "^11.0.2", + "ip": "^1.1.5", + "lodash": "^4.17.21", + "node-fetch": "^2.6.7", + "open": "^8.4.0", + "pretty-hrtime": "^1.0.3", + "prompts": "^2.4.0", + "regenerator-runtime": "^0.13.7", + "serve-favicon": "^2.5.0", + "slash": "^3.0.0", + "telejson": "^6.0.8", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2", + "watchpack": "^2.2.0", + "webpack": "4", + "ws": "^8.2.3", + "x-default-browser": "^0.4.0" + }, + "dependencies": { + "@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "dev": true, + "requires": { + "core-js": "^3.6.5", + "find-up": "^4.1.0" + } + }, + "@types/node": { + "version": "16.11.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz", + "integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + }, + "enhanced-resolve": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + } + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "webpack": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", + "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.5.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "watchpack": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "dev": true, + "requires": { + "chokidar": "^3.4.1", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" + } + } + } + }, + "ws": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz", + "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@storybook/csf": { + "version": "0.0.2--canary.4566f4d.1", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz", + "integrity": "sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "@storybook/csf-tools": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-6.5.4.tgz", + "integrity": "sha512-mCAO8Ddig5PzXiI4HxiWhdLmPPpaovzVxqdh7g31k3fhpnRkQrKwftNGTW/ArFDdisMjB1+gm+ZOVACZg0pO4w==", + "dev": true, + "requires": { + "@babel/core": "^7.12.10", + "@babel/generator": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/plugin-transform-react-jsx": "^7.12.12", + "@babel/preset-env": "^7.12.11", + "@babel/traverse": "^7.12.11", + "@babel/types": "^7.12.11", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/mdx1-csf": "^0.0.1-canary.1.eed86d7.0", + "core-js": "^3.8.2", + "fs-extra": "^9.0.1", + "global": "^4.4.0", + "regenerator-runtime": "^0.13.7", + "ts-dedent": "^2.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + } + } + }, + "@storybook/docs-tools": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/docs-tools/-/docs-tools-6.5.4.tgz", + "integrity": "sha512-XiCiCKwalbPrhcD9jhMM9IMOdOplUPNnwhW//VmHvWJ2j+JNiQVX42vfecl4hKy8wx63S/AzaGKw2yuavUAiHQ==", + "dev": true, + "requires": { + "@babel/core": "^7.12.10", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/store": "6.5.4", + "core-js": "^3.8.2", + "doctrine": "^3.0.0", + "lodash": "^4.17.21", + "regenerator-runtime": "^0.13.7" + } + }, + "@storybook/instrumenter": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-6.5.4.tgz", + "integrity": "sha512-lCpg71qQsZV7uwEECPlbdro0FPKiJG/RykWTtvrj03l+r0OaHhfGos9Bm1ClooJUkMovo+sBxz6/8OxeuCCHSA==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/core-events": "6.5.4", + "core-js": "^3.8.2", + "global": "^4.4.0" + } + }, + "@storybook/manager-webpack4": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/manager-webpack4/-/manager-webpack4-6.5.4.tgz", + "integrity": "sha512-JgFAlnnsOV6+L0ShDApwbDDdzoEAwuy01+TLUVdl60rlpbcBaasBMkZYAy1iwKZ59gCT51GocGMGDXBVeUJq9g==", + "dev": true, + "requires": { + "@babel/core": "^7.12.10", + "@babel/plugin-transform-template-literals": "^7.12.1", + "@babel/preset-react": "^7.12.10", + "@storybook/addons": "6.5.4", + "@storybook/core-client": "6.5.4", + "@storybook/core-common": "6.5.4", + "@storybook/node-logger": "6.5.4", + "@storybook/theming": "6.5.4", + "@storybook/ui": "6.5.4", + "@types/node": "^14.0.10 || ^16.0.0", + "@types/webpack": "^4.41.26", + "babel-loader": "^8.0.0", + "case-sensitive-paths-webpack-plugin": "^2.3.0", + "chalk": "^4.1.0", + "core-js": "^3.8.2", + "css-loader": "^3.6.0", + "express": "^4.17.1", + "file-loader": "^6.2.0", + "find-up": "^5.0.0", + "fs-extra": "^9.0.1", + "html-webpack-plugin": "^4.0.0", + "node-fetch": "^2.6.7", + "pnp-webpack-plugin": "1.6.4", + "read-pkg-up": "^7.0.1", + "regenerator-runtime": "^0.13.7", + "resolve-from": "^5.0.0", + "style-loader": "^1.3.0", + "telejson": "^6.0.8", + "terser-webpack-plugin": "^4.2.3", + "ts-dedent": "^2.0.0", + "url-loader": "^4.1.1", + "util-deprecate": "^1.0.2", + "webpack": "4", + "webpack-dev-middleware": "^3.7.3", + "webpack-virtual-modules": "^0.2.2" + }, + "dependencies": { + "@types/node": { + "version": "16.11.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz", + "integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "css-loader": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^6.3.0" + } + }, + "enhanced-resolve": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "dev": true, + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "style-loader": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz", + "integrity": "sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.7.0" + }, + "dependencies": { + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, + "loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "terser-webpack-plugin": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", + "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", + "dev": true, + "requires": { + "cacache": "^15.0.5", + "find-cache-dir": "^3.3.1", + "jest-worker": "^26.5.0", + "p-limit": "^3.0.2", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", + "source-map": "^0.6.1", + "terser": "^5.3.4", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "watchpack": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "dev": true, + "requires": { + "chokidar": "^3.4.1", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" + } + }, + "webpack": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", + "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.5.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + } + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + } + } + } + }, + "webpack-dev-middleware": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", + "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", + "dev": true, + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@storybook/manager-webpack5": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/manager-webpack5/-/manager-webpack5-6.5.4.tgz", + "integrity": "sha512-UwGnwkHV5CFuHmrsQnjQ0IQj6uXZFopCB7e96HU0YwOBiBA9ABlbJpUrxC/N7vO+OhTNtH6lcwHx0eJbbnnuxw==", + "dev": true, + "requires": { + "@babel/core": "^7.12.10", + "@babel/plugin-transform-template-literals": "^7.12.1", + "@babel/preset-react": "^7.12.10", + "@storybook/addons": "6.5.4", + "@storybook/core-client": "6.5.4", + "@storybook/core-common": "6.5.4", + "@storybook/node-logger": "6.5.4", + "@storybook/theming": "6.5.4", + "@storybook/ui": "6.5.4", + "@types/node": "^14.0.10 || ^16.0.0", + "babel-loader": "^8.0.0", + "case-sensitive-paths-webpack-plugin": "^2.3.0", + "chalk": "^4.1.0", + "core-js": "^3.8.2", + "css-loader": "^5.0.1", + "express": "^4.17.1", + "find-up": "^5.0.0", + "fs-extra": "^9.0.1", + "html-webpack-plugin": "^5.0.0", + "node-fetch": "^2.6.7", + "process": "^0.11.10", + "read-pkg-up": "^7.0.1", + "regenerator-runtime": "^0.13.7", + "resolve-from": "^5.0.0", + "style-loader": "^2.0.0", + "telejson": "^6.0.8", + "terser-webpack-plugin": "^5.0.3", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2", + "webpack": "^5.9.0", + "webpack-dev-middleware": "^4.1.0", + "webpack-virtual-modules": "^0.4.1" + }, + "dependencies": { + "@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true + }, + "@types/node": { + "version": "16.11.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz", + "integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "clean-css": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", + "integrity": "sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + }, + "css-loader": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", + "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.15", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.5" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "requires": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + } + }, + "html-webpack-plugin": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", + "dev": true, + "requires": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + } + }, + "htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "requires": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "requires": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "style-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", + "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "terser": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.13.1.tgz", + "integrity": "sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA==", + "dev": true, + "requires": { + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map": "~0.8.0-beta.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "requires": { + "whatwg-url": "^7.0.0" + } + } + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "webpack-dev-middleware": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-4.3.0.tgz", + "integrity": "sha512-PjwyVY95/bhBh6VUqt6z4THplYcsvQ8YNNBTBM873xLVmw8FLeALn0qurHbs9EmcfhzQis/eoqypSnZeuUz26w==", + "dev": true, + "requires": { + "colorette": "^1.2.2", + "mem": "^8.1.1", + "memfs": "^3.2.2", + "mime-types": "^2.1.30", + "range-parser": "^1.2.1", + "schema-utils": "^3.0.0" + } + }, + "webpack-virtual-modules": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.3.tgz", + "integrity": "sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==", + "dev": true + }, + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "@storybook/mdx1-csf": { + "version": "0.0.1-canary.1.eed86d7.0", + "resolved": "https://registry.npmjs.org/@storybook/mdx1-csf/-/mdx1-csf-0.0.1-canary.1.eed86d7.0.tgz", + "integrity": "sha512-KOoTWeseRyJYU5qzEWahgNRhRLgHzPrWx4jIiK4oI9MmNx81cD0B0o24Gq04Iyt89X4jTk+VPHc5REJO1Gs99Q==", + "dev": true, + "requires": { + "@babel/generator": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/preset-env": "^7.12.11", + "@babel/types": "^7.12.11", + "@mdx-js/mdx": "^1.6.22", + "@types/lodash": "^4.14.167", + "js-string-escape": "^1.0.1", + "loader-utils": "^2.0.0", + "lodash": "^4.17.21", + "prettier": ">=2.2.1 <=2.3.0", + "ts-dedent": "^2.0.0" + } + }, + "@storybook/node-logger": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-6.5.4.tgz", + "integrity": "sha512-2rK9oTBhvRm4VOr1ywIWXfWEXVbuV7wGdZu4hpSDlkhnBBFCyYBJH9asEJVoB4Qk77HPMCqkw2IHpstVjkASXQ==", + "dev": true, + "requires": { + "@types/npmlog": "^4.1.2", + "chalk": "^4.1.0", + "core-js": "^3.8.2", + "npmlog": "^5.0.1", + "pretty-hrtime": "^1.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "dev": true, + "requires": { + "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" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "dev": true, + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@storybook/postinstall": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/postinstall/-/postinstall-6.5.4.tgz", + "integrity": "sha512-euiZOwz5KmtL3wcSxzxLTkGkxLCroZTVaNt/NopRBa0F8erJfT9MiQPVw/xFkS9QwEViaR+Zq4KmStxB41zycQ==", + "dev": true, + "requires": { + "core-js": "^3.8.2" + } + }, + "@storybook/preview-web": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/preview-web/-/preview-web-6.5.4.tgz", + "integrity": "sha512-mbyZI0ProlLSB5MVe+YvoCt+XtlyBvP3lzTVdPZWVVtRN6DK5SEcfsK9//rgmqxiYYXfSKfyI53skYZkKAIEcA==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/channel-postmessage": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/store": "6.5.4", + "ansi-to-html": "^0.6.11", + "core-js": "^3.8.2", + "global": "^4.4.0", + "lodash": "^4.17.21", + "qs": "^6.10.0", + "regenerator-runtime": "^0.13.7", + "synchronous-promise": "^2.0.15", + "ts-dedent": "^2.0.0", + "unfetch": "^4.2.0", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/router": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.5.4.tgz", + "integrity": "sha512-Xw0oYaJSxd/zPUiZw8AoQRCdrePuxsn1XwlDvzfNlMnPnU17LAFzVf8kpHPXvx01F9pLnGI4ZggsjVomHy/E1Q==", + "dev": true, + "requires": { + "@storybook/client-logger": "6.5.4", + "core-js": "^3.8.2", + "regenerator-runtime": "^0.13.7" + } + }, + "@storybook/source-loader": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-6.5.4.tgz", + "integrity": "sha512-NfNktWLNvsGVyf3IjCke/4HftPcIHD251J7QOuPjq3lD9OTLZgL4blqscUmd2RrGZRUEH+KSfCIV9jTSeqS1wQ==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "core-js": "^3.8.2", + "estraverse": "^5.2.0", + "global": "^4.4.0", + "loader-utils": "^2.0.0", + "lodash": "^4.17.21", + "prettier": ">=2.2.1 <=2.3.0", + "regenerator-runtime": "^0.13.7" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "@storybook/store": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/store/-/store-6.5.4.tgz", + "integrity": "sha512-hCHSQorxRVGseVlIfM4JXbJHkA6AAXuAFl+QsUTjG24q3zAc3sIE2nbH5KsHuJN5NorjcrGeLQOAO9amqzUeHA==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "core-js": "^3.8.2", + "fast-deep-equal": "^3.1.3", + "global": "^4.4.0", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "regenerator-runtime": "^0.13.7", + "slash": "^3.0.0", + "stable": "^0.1.8", + "synchronous-promise": "^2.0.15", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + } + }, + "@storybook/telemetry": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-6.5.4.tgz", + "integrity": "sha512-BKQajMd80mXHp/sE54I1JiusgvfZrnRqBbZW93slGNG3JyWpJLGOBH/KCp4iyu6Htbqssi+nvrxneUCT+wA6eQ==", + "dev": true, + "requires": { + "@storybook/client-logger": "6.5.4", + "@storybook/core-common": "6.5.4", + "chalk": "^4.1.0", + "core-js": "^3.8.2", + "detect-package-manager": "^2.0.1", + "fetch-retry": "^5.0.2", + "fs-extra": "^9.0.1", + "global": "^4.4.0", + "isomorphic-unfetch": "^3.1.0", + "nanoid": "^3.3.1", + "read-pkg-up": "^7.0.1", + "regenerator-runtime": "^0.13.7" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@storybook/testing-library": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@storybook/testing-library/-/testing-library-0.0.11.tgz", + "integrity": "sha512-8KbKx3s1e+uF3oWlPdyXRpZa6xtCsCHtXh1nCTisMA6P5YcSDaCg59NXIOVIQCAwKvjRomlqMJH8JL1WyOzeVg==", + "dev": true, + "requires": { + "@storybook/client-logger": "^6.4.0 || >=6.5.0-0", + "@storybook/instrumenter": "^6.4.0 || >=6.5.0-0", + "@testing-library/dom": "^8.3.0", + "@testing-library/user-event": "^13.2.1", + "ts-dedent": "^2.2.0" + } + }, + "@storybook/theming": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.5.4.tgz", + "integrity": "sha512-J5ipCqKH8FfMVj8SuVsjxSIYgb9GN8NBsVAVvTakVjqyKnv5znR+w+h0lvyzRNaEnF5LQCy7Uj7DE6+Pbg/wPw==", + "dev": true, + "requires": { + "@storybook/client-logger": "6.5.4", + "core-js": "^3.8.2", + "regenerator-runtime": "^0.13.7" + } + }, + "@storybook/ui": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@storybook/ui/-/ui-6.5.4.tgz", + "integrity": "sha512-+kMgNMcW7xma/H/O4LFzdxjX6GPfEEvOkm3HQDz0dJW7z/wtQVTgZKCA8gAVl+eNcNyRGzYRKOrOU0AzFfU7lw==", + "dev": true, + "requires": { + "@storybook/addons": "6.5.4", + "@storybook/api": "6.5.4", + "@storybook/channels": "6.5.4", + "@storybook/client-logger": "6.5.4", + "@storybook/components": "6.5.4", + "@storybook/core-events": "6.5.4", + "@storybook/router": "6.5.4", + "@storybook/semver": "^7.3.2", + "@storybook/theming": "6.5.4", + "core-js": "^3.8.2", + "regenerator-runtime": "^0.13.7", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "dev": true, + "requires": { + "core-js": "^3.6.5", + "find-up": "^4.1.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@testing-library/dom": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.13.0.tgz", + "integrity": "sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^5.0.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.4.4", + "pretty-format": "^27.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + } + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, + "@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true + }, + "@ts-morph/common": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.12.3.tgz", + "integrity": "sha512-4tUmeLyXJnJWvTFOKtcNJ1yh0a3SsTLi2MUoyj8iUNznFRN1ZquaNe7Oukqrnki2FzZkm0J9adCNLDZxUzvj+w==", + "dev": true, + "requires": { + "fast-glob": "^3.2.7", + "minimatch": "^3.0.4", + "mkdirp": "^1.0.4", + "path-browserify": "^1.0.1" + } + }, + "@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, + "@types/component-emitter": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", + "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", + "dev": true + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, + "@types/eslint": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.2.tgz", + "integrity": "sha512-Z1nseZON+GEnFjJc04sv4NSALGjhFwy6K0HXt7qsn5ArfAKtb63dXNJHf+1YW6IpOIYRBGUbu3GwJdj8DGnCjA==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", + "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "dev": true + }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, + "@types/html-minifier-terser": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", + "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==", + "dev": true + }, + "@types/is-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.1.tgz", + "integrity": "sha512-A79HEEiwXTFtfY+Bcbo58M2GRYzCr9itHWzbzHVFNEYCcoU/MMGwYYf721gBrnhpj1s6RGVVha/IgNFnR0Iw/Q==", + "dev": true + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jasmine": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.8.2.tgz", + "integrity": "sha512-u5h7dqzy2XpXTzhOzSNQUQpKGFvROF8ElNX9P/TJvsHnTg/JvsAseVsGWQAQQldqanYaM+5kwxW909BBFAUYsg==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "@types/lodash": { + "version": "4.14.182", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", + "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", + "dev": true + }, + "@types/mdast": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "@types/node": { + "version": "12.20.52", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.52.tgz", + "integrity": "sha512-cfkwWw72849SNYp3Zx0IcIs25vABmFh73xicxhCkTcvtZQeIez15PpwQN8fY3RD7gv1Wrxlc9MEtfMORZDEsGw==", + "dev": true + }, + "@types/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "@types/npmlog": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/npmlog/-/npmlog-4.1.4.tgz", + "integrity": "sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", + "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==", + "dev": true + }, + "@types/pretty-hrtime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/pretty-hrtime/-/pretty-hrtime-1.0.1.tgz", + "integrity": "sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/react": { + "version": "18.0.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.9.tgz", + "integrity": "sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "16.9.16", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.16.tgz", + "integrity": "sha512-Oqc0RY4fggGA3ltEgyPLc3IV9T73IGoWjkONbsyJ3ZBn+UPPCYpU2ec0i3cEbJuEdZtkqcCF2l1zf2pBdgUGSg==", + "dev": true, + "requires": { + "@types/react": "^16" + }, + "dependencies": { + "@types/react": { + "version": "16.14.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.26.tgz", + "integrity": "sha512-c/5CYyciOO4XdFcNhZW1O2woVx86k4T+DO2RorHZL7EhitkNQgSD/SgpdZJAUJa/qjVgOmTM44gHkAdZSXeQuQ==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + } + } + }, + "@types/react-syntax-highlighter": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.5.tgz", + "integrity": "sha512-VIOi9i2Oj5XsmWWoB72p3KlZoEbdRAcechJa8Ztebw7bDl2YmR+odxIqhtJGp1q2EozHs02US+gzxJ9nuf56qg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/tapable": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", + "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==", + "dev": true + }, + "@types/uglify-js": { + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.2.tgz", + "integrity": "sha512-/xFrPIo+4zOeNGtVMbf9rUm0N+i4pDf1ynExomqtokIJmVzR3962lJ1UE+MmexMkA0cmN9oTzg5Xcbwge0Ij2Q==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, + "@types/webpack": { + "version": "4.41.32", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", + "integrity": "sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/tapable": "^1", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "anymatch": "^3.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@types/webpack-env": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.17.0.tgz", + "integrity": "sha512-eHSaNYEyxRA5IAG0Ym/yCyf86niZUIF/TpWKofQI/CVfh5HsMEUyfE2kwFxha4ow0s5g0LfISQxpDKjbRDrizw==", + "dev": true + }, + "@types/webpack-sources": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.9.tgz", + "integrity": "sha512-bvzMnzqoK16PQIC8AYHNdW45eREJQMd6WG/msQWX5V2+vZmODCOPb4TJcbgRljTZZTwTM4wUMcsI8FftNA7new==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@types/yargs": { + "version": "15.0.14", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", + "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", + "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.33.0", + "@typescript-eslint/scope-manager": "4.33.0", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", + "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", + "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.0", + "debug": "^4.3.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", + "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0" + } + }, + "@typescript-eslint/types": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", + "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", + "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.26.0.tgz", + "integrity": "sha512-PJFwcTq2Pt4AMOKfe3zQOdez6InIDOjUJJD3v3LyEtxHGVVRK3Vo7Dd923t/4M9hSH2q2CLvcTdxlLPjcIk3eg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.26.0", + "@typescript-eslint/types": "5.26.0", + "@typescript-eslint/typescript-estree": "5.26.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.26.0.tgz", + "integrity": "sha512-gVzTJUESuTwiju/7NiTb4c5oqod8xt5GhMbExKsCTp6adU3mya6AGJ4Pl9xC7x2DX9UYFsjImC0mA62BCY22Iw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.26.0", + "@typescript-eslint/visitor-keys": "5.26.0" + } + }, + "@typescript-eslint/types": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.26.0.tgz", + "integrity": "sha512-8794JZFE1RN4XaExLWLI2oSXsVImNkl79PzTOOWt9h0UHROwJedNOD2IJyfL0NbddFllcktGIO2aOu10avQQyA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.26.0.tgz", + "integrity": "sha512-EyGpw6eQDsfD6jIqmXP3rU5oHScZ51tL/cZgFbFBvWuCwrIptl+oueUZzSmLtxFuSOQ9vDcJIs+279gnJkfd1w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.26.0", + "@typescript-eslint/visitor-keys": "5.26.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.26.0.tgz", + "integrity": "sha512-wei+ffqHanYDOQgg/fS6Hcar6wAWv0CUPQ3TZzOWd2BLfgP539rb49bwua8WRAs7R6kOSLn82rfEu2ro6Llt8Q==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.26.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", + "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.33.0", + "eslint-visitor-keys": "^2.0.0" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.9.0" + }, + "dependencies": { + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + } + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0" + }, + "dependencies": { + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + } + } + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + }, + "dependencies": { + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + } + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==" + }, + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "address": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.0.tgz", + "integrity": "sha512-tNEZYz5G/zYunxFm7sfhAxkXEuLj3K6BKwv6ZURlsF6yiUQ65z0Q2wZW9L5cPUl9ocofGvXOdFYbFHp0+6MOig==", + "dev": true + }, + "adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + } + }, + "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==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + } + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "airbnb-js-shims": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/airbnb-js-shims/-/airbnb-js-shims-2.2.1.tgz", + "integrity": "sha512-wJNXPH66U2xjgo1Zwyjf9EydvJ2Si94+vSdk6EERcBfB2VZkeltpqIats0cqIZMLCXP3zcyaUKGYQeIBT6XjsQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "array.prototype.flat": "^1.2.1", + "array.prototype.flatmap": "^1.2.1", + "es5-shim": "^4.5.13", + "es6-shim": "^0.35.5", + "function.prototype.name": "^1.1.0", + "globalthis": "^1.0.0", + "object.entries": "^1.1.0", + "object.fromentries": "^2.0.0 || ^1.0.0", + "object.getownpropertydescriptors": "^2.0.3", + "object.values": "^1.1.0", + "promise.allsettled": "^1.0.0", + "promise.prototype.finally": "^3.1.0", + "string.prototype.matchall": "^4.0.0 || ^3.0.1", + "string.prototype.padend": "^3.0.0", + "string.prototype.padstart": "^3.0.0", + "symbol.prototype.description": "^1.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-formats": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", + "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "dev": true, + "optional": true + }, + "ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "requires": { + "string-width": "^4.1.0" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansi-to-html": { + "version": "0.6.15", + "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.6.15.tgz", + "integrity": "sha512-28ijx2aHJGdzbs+O5SNQF65r6rrKYnkuwTYm8lZlChuoJ9P1vVzIpWO20sQTqTPDXYp6NFwk326vApTtLVFXpQ==", + "dev": true, + "requires": { + "entities": "^2.0.0" + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", + "dev": true + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "apache-crypt": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.5.tgz", + "integrity": "sha512-ICnYQH+DFVmw+S4Q0QY2XRXD8Ne8ewh8HgbuFH4K7022zCxgHM0Hz1xkRnUlEfAXNbwp1Cnhbedu60USIfDxvg==", + "dev": true, + "requires": { + "unix-crypt-td-js": "^1.1.4" + } + }, + "apache-md5": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.7.tgz", + "integrity": "sha512-JtHjzZmJxtzfTSjsCyHgPR155HBe5WGyUyHTaEkfy46qhwCFKx1Epm6nAxgUG3WfUZP1dWhGqj9Z2NOBeZ+uBw==", + "dev": true + }, + "app-root-dir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", + "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", + "dev": true + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "are-we-there-yet": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz", + "integrity": "sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "optional": true + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", + "dev": true + }, + "array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true + }, + "array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.map": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.4.tgz", + "integrity": "sha512-Qds9QnX7A0qISY7JT5WuJO0NJPE9CMlC6JzHQfhpqAAQQzufVRoeH7EzUY5GcPTx72voG8LV/5eo+b8Qi8hmhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + } + }, + "array.prototype.reduce": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", + "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + } + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true + }, + "ast-transform": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/ast-transform/-/ast-transform-0.0.0.tgz", + "integrity": "sha512-e/JfLiSoakfmL4wmTGPjv0HpTICVmxwXgYOB8x+mzozHL8v+dSfCbrJ8J8hJ0YBP0XcYu1aLZ6b/3TnxNK3P2A==", + "dev": true, + "requires": { + "escodegen": "~1.2.0", + "esprima": "~1.0.4", + "through": "~2.3.4" + }, + "dependencies": { + "escodegen": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.2.0.tgz", + "integrity": "sha1-Cd55Z3kcyVi3+Jot220jRRrzJ+E=", + "dev": true, + "requires": { + "esprima": "~1.0.4", + "estraverse": "~1.5.0", + "esutils": "~1.0.0", + "source-map": "~0.1.30" + } + }, + "esprima": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", + "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", + "dev": true + }, + "estraverse": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", + "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=", + "dev": true + }, + "esutils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", + "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=", + "dev": true + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "ast-types": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.7.8.tgz", + "integrity": "sha512-RIOpVnVlltB6PcBJ5BMLx+H+6JJ/zjDGU0t7f0L6c2M1dqcK92VQopLBlPQ9R80AVXelfqYgjcPLtHtDbNFg0Q==", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "9.8.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", + "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", + "dev": true, + "requires": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "picocolors": "^0.2.1", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "babel-loader": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "babel-plugin-apply-mdx-type-prop": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz", + "integrity": "sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.10.4", + "@mdx-js/util": "1.6.22" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-extract-import-names": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz", + "integrity": "sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true + } + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "dependencies": { + "istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + } + }, + "babel-plugin-named-exports-order": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-named-exports-order/-/babel-plugin-named-exports-order-0.0.2.tgz", + "integrity": "sha512-OgOYHOLoRK+/mvXU9imKHlG6GkPLYrUCvFXG/CM93R/aNNO8pOOF4aS+S8CCHMDQoNSeiOYEZb/G6RwL95Jktw==", + "dev": true + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.3.tgz", + "integrity": "sha512-NDZ0auNRzmAfE1oDDPW2JhzIMXUk+FFe2ICejmt5T4ocKgiQx3e0VCRx9NCAidcMtL2RUZaWtXnmjTCkx0tcbA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.4", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.5.tgz", + "integrity": "sha512-ninF5MQNwAX9Z7c9ED+H2pGt1mXdP4TqzlHKyPIYmJIYz0N+++uwdM7RnJukklhzJ54Q84vA4ZJkgs7lu5vqcw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.2", + "core-js-compat": "^3.16.2" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.3.tgz", + "integrity": "sha512-JVE78oRZPKFIeUqFGrSORNzQnrDwZR16oiWeGM8ZyjBn2XAT5OjP+wXx5ESuo33nUsFUEJYjtklnsKbxW5L+7g==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.4" + } + }, + "bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "dev": true + }, + "better-opn": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-2.1.1.tgz", + "integrity": "sha512-kIPXZS5qwyKiX/HcRvDYfmBQUa8XP17I0mYZZ0y4UhpYOSvtsLHDYqmomS+Mj20aDvD3knEiQ0ecQy2nhio3yA==", + "dev": true, + "requires": { + "open": "^7.0.3" + }, + "dependencies": { + "open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + } + } + }, + "big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "optional": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", + "dev": true + }, + "body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg==", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", "deep-equal": "^1.0.1", "dns-equal": "^1.0.0", "dns-txt": "^2.0.2", @@ -3019,210 +10519,3345 @@ "multicast-dns-service-types": "^1.1.0" } }, - "boolbase": { + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "bplist-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.1.tgz", + "integrity": "sha512-2AEM0FXy8ZxVLBuqX0hqt1gDwcnz2zygEkQ6zaD5Wko/sB9paUNwlpawrFtKeHUAQUOzjVy9AO4oeonqIHKA9Q==", + "dev": true, + "optional": true, + "requires": { + "big-integer": "^1.6.7" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "brfs": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brfs/-/brfs-2.0.2.tgz", + "integrity": "sha512-IrFjVtwu4eTJZyu8w/V2gxU7iLTtcHih67sgEdzrhjLBMHp2uYefUBfdM4k2UvcuWMgV7PQDZHSLeNWnLFKWVQ==", + "dev": true, + "requires": { + "quote-stream": "^1.0.1", + "resolve": "^1.1.5", + "static-module": "^3.0.2", + "through2": "^2.0.0" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "brotli": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.2.tgz", + "integrity": "sha512-K0HNa0RRpUpcF8yS4yNSd6vmkrvA+wRd+symIcwhfqGLAi7YgGlKfO4oDYVgiahiLGNviO9uY7Zlb1MCPeTmSA==", + "dev": true, + "requires": { + "base64-js": "^1.1.2" + } + }, + "browser-assert": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", + "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-optional/-/browserify-optional-1.0.1.tgz", + "integrity": "sha512-VrhjbZ+Ba5mDiSYEuPelekQMfTbhcA2DhLk2VQWqdcCROWeFqlTcXZ7yfRkXCIl8E+g4gINJYJiRB7WEtfomAQ==", + "dev": true, + "requires": { + "ast-transform": "0.0.0", + "ast-types": "^0.7.0", + "browser-resolve": "^1.8.1" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", + "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001332", + "electron-to-chromium": "^1.4.118", + "escalade": "^3.1.1", + "node-releases": "^2.0.3", + "picocolors": "^1.0.0" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", + "dev": true + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true + }, + "cacache": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.2.0.tgz", + "integrity": "sha512-uKoJSHmnrqXgthDFx/IU6ED/5xd+NNGe+Bb+kLZy7Ku4P+BaiWEUflAKPZ7eAzsYGcsAGASJZsybXp+quEcHTw==", + "dev": true, + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw==", + "dev": true + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "requires": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==", + "dev": true, + "optional": true + } + } + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001342", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001342.tgz", + "integrity": "sha512-bn6sOCu7L7jcbBbyNhLg0qzXdJ/PMbybZTH/BA6Roet9wxYRm6Tr9D0s0uhLkOZ6MSG+QU6txUgdpr3MXIVqjA==", + "dev": true + }, + "canonical-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz", + "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==", + "dev": true + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "requires": { + "rsvp": "^4.8.4" + } + }, + "case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "dev": true + }, + "ccount": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", + "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true + }, + "character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true + }, + "character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true + }, + "charcodes": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/charcodes/-/charcodes-0.2.0.tgz", + "integrity": "sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ==", + "dev": true + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "cheerio": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.11.tgz", + "integrity": "sha512-bQwNaDIBKID5ts/DsdhxrjqFXYfLw4ste+wMKqWA8DyKcS4qwsPP4Bk8ZNaTJjvpiX/qW3BT4sU7d6Bh5i+dag==", + "dev": true, + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "tslib": "^2.4.0" + }, + "dependencies": { + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dev": true, + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + } + }, + "entities": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.0.tgz", + "integrity": "sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg==", + "dev": true + }, + "parse5": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", + "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", + "dev": true, + "requires": { + "entities": "^4.3.0" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + } + } + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "dependencies": { + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dev": true, + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + } + }, + "entities": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.0.tgz", + "integrity": "sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg==", + "dev": true + } + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-dependency-plugin": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz", + "integrity": "sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "dev": true + }, + "cli-table3": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", + "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "clsx": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.0.tgz", + "integrity": "sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==", + "dev": true + }, + "code-block-writer": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-11.0.0.tgz", + "integrity": "sha512-GEqWvEWWsOvER+g9keO4ohFoD3ymwyCnqY3hoTr7GZipYFwEhMHJw+TtV0rfgRhNImM6QWZGO2XYjlJVyYT62w==", + "dev": true, + "requires": { + "tslib": "2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "collapse-white-space": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "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==", + "dev": true + }, + "colord": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz", + "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==", + "dev": true + }, + "colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "dev": true + }, + "commander": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + } + } + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "requires": { + "is-what": "^3.14.1" + } + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-webpack-plugin": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-9.0.1.tgz", + "integrity": "sha512-14gHKKdYIxF84jCEgPgYXCPpldbwpxxLbCmA7LReY7gvbaT555DgeBWBgBZM116tv/fO6RRJrsivBqRyRlukhw==", + "dev": true, + "requires": { + "fast-glob": "^3.2.5", + "glob-parent": "^6.0.0", + "globby": "^11.0.3", + "normalize-path": "^3.0.0", + "p-limit": "^3.1.0", + "schema-utils": "^3.0.0", + "serialize-javascript": "^6.0.0" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + } + } + }, + "core-js": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.0.tgz", + "integrity": "sha512-5+5VxRFmSf97nM8Jr2wzOwLqRo6zphH2aX+7KsAUONObyzakDNq2G/bgbhinxB4PoV9L3aXQYhiDKyIKWd2c8g==", + "dev": true + }, + "core-js-compat": { + "version": "3.22.6", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.22.6.tgz", + "integrity": "sha512-dQ/SxlHcuiywaPIoSUCU6Fx+Mk/H5TXENqd/ZJcK85ta0ZcQkbzHwblxPeL0hF5o+NsT2uK3q9ZOG5TboiVuWw==", + "dev": true, + "requires": { + "browserslist": "^4.20.3", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@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" + } + }, + "cp-file": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-7.0.0.tgz", + "integrity": "sha512-0Cbj7gyvFVApzpK/uhCtQ/9kE9UnYpxMzaq5nQQC/Dh4iaj5fxp7iEFIullrYwzj8nf0qnsI1Qsx34hAeAebvw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "nested-error-stacks": "^2.0.0", + "p-event": "^4.1.0" + } + }, + "cpy": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/cpy/-/cpy-8.1.2.tgz", + "integrity": "sha512-dmC4mUesv0OYH2kNFEidtf/skUwv4zePmGeepjyyJ0qTo5+8KhA1o99oIAwVVLzQMAeDJml74d6wPPKb6EZUTg==", + "dev": true, + "requires": { + "arrify": "^2.0.1", + "cp-file": "^7.0.0", + "globby": "^9.2.0", + "has-glob": "^1.0.0", + "junk": "^3.1.0", + "nested-error-stacks": "^2.1.0", + "p-all": "^2.1.0", + "p-filter": "^2.1.0", + "p-map": "^3.0.0" + }, + "dependencies": { + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + } + }, + "fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "dev": true, + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globby": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "critters": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.12.tgz", + "integrity": "sha512-ujxKtKc/mWpjrOKeaACTaQ1aP0O31M0ZPWhfl85jZF1smPU4Ivb9va5Ox2poif4zVJQQo0LCFlzGtEZAsCAPcw==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "css-select": "^4.1.3", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "postcss": "^8.3.7", + "pretty-bytes": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==", + "dev": true + }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-blank-pseudo": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", + "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-declaration-sorter": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz", + "integrity": "sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==", + "dev": true + }, + "css-has-pseudo": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", + "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^5.0.0-rc.4" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.2.0.tgz", + "integrity": "sha512-/rvHfYRjIpymZblf49w8jYcRo2y9gj6rV8UroHGmBxKrIyGLokpycyKzp9OkitvqT29ZSpzJ0Ic7SpnJX3sC8g==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.2.15", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.1.0", + "semver": "^7.3.5" + } + }, + "css-minimizer-webpack-plugin": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.0.2.tgz", + "integrity": "sha512-B3I5e17RwvKPJwsxjjWcdgpU/zqylzK1bPVghcmpFHRL48DXiBgrtqz1BJsn68+t/zzaLp9kYAaEDvQ7GyanFQ==", + "dev": true, + "requires": { + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "p-limit": "^3.0.2", + "postcss": "^8.3.5", + "schema-utils": "^3.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-parse": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", + "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", + "dev": true, + "requires": { + "css": "^2.0.0" + } + }, + "css-prefers-color-scheme": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", + "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "cssdb": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", + "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==", + "dev": true + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "cssnano": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.9.tgz", + "integrity": "sha512-hctQHIIeDrfMjq0bQhoVmRVaSeNNOGxkvkKVOcKpJzLr09wlRrZWH4GaYudp0aszpW8wJeaO5/yBmID9n7DNCg==", + "dev": true, + "requires": { + "cssnano-preset-default": "^5.2.9", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + } + }, + "cssnano-preset-default": { + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.9.tgz", + "integrity": "sha512-/4qcQcAfFEg+gnXE5NxKmYJ9JcT+8S5SDuJCLYMDN8sM/ymZ+lgLXq5+ohx/7V2brUCkgW2OaoCzOdAN0zvhGw==", + "dev": true, + "requires": { + "css-declaration-sorter": "^6.2.2", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.0", + "postcss-convert-values": "^5.1.1", + "postcss-discard-comments": "^5.1.1", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.5", + "postcss-merge-rules": "^5.1.1", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.3", + "postcss-minify-selectors": "^5.2.0", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.0", + "postcss-normalize-repeat-style": "^5.1.0", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.0", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.1", + "postcss-reduce-initial": "^5.1.0", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + } + }, + "cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "dev": true + }, + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "requires": { + "css-tree": "^1.1.2" + } + }, + "csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "optional": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", + "dev": true + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dash-ast": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-2.0.1.tgz", + "integrity": "sha512-5TXltWJGc+RdnabUGzhRae1TRq6m4gr+3K2wQX0is5/F2yS6MJXJvLyI3ErAnsAXuJoGqvfVD5icRgim07DrxQ==", + "dev": true + }, + "date-format": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.10.tgz", + "integrity": "sha512-RuMIHocrVjF84bUSTcd1uokIsLsOsk1Awb7TexNOI3f48ukCu39mjslWquDTA08VaDMF2umr3MB9ow5EyJTWyA==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decache": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/decache/-/decache-4.6.1.tgz", + "integrity": "sha512-ohApBM8u9ygepJCjgBrEZSSxPjc0T/PJkD+uNyxXPkqudyUpdXpwJYp0VISm2WrPVzASU6DZyIi6BWdyw7uJ2Q==", + "dev": true, + "requires": { + "callsite": "^1.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "default-browser-id": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-1.0.4.tgz", + "integrity": "sha1-5Z0JpdFXuCi4dsJoFuYcPSosIDo=", + "dev": true, + "optional": true, + "requires": { + "bplist-parser": "^0.1.0", + "meow": "^3.1.0", + "untildify": "^2.0.0" + } + }, + "default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + } + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "detab": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.4.tgz", + "integrity": "sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==", + "dev": true, + "requires": { + "repeat-string": "^1.5.4" + } + }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "detect-package-manager": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-2.0.1.tgz", + "integrity": "sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==", + "dev": true, + "requires": { + "execa": "^5.1.1" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "detect-port": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.3.0.tgz", + "integrity": "sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ==", + "dev": true, + "requires": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "dev": true + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", + "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-accessibility-api": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz", + "integrity": "sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==", + "dev": true + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", + "dev": true + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "requires": { + "domelementtype": "^2.2.0" + } + }, + "dommatrix": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz", + "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==", + "dev": true + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "dot": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dot/-/dot-1.1.3.tgz", + "integrity": "sha512-/nt74Rm+PcfnirXGEdhZleTwGC2LMnuKTeeTIlI82xb5loBBoXNYzr2ezCroPSMtilK8EZIfcNZwOcHN+ib1Lg==", + "dev": true + }, + "dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true + }, + "dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.137", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz", + "integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==", + "dev": true + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", + "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", + "dev": true, + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true + } + } + }, + "engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "dev": true + }, + "enhanced-resolve": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz", + "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + } + }, + "es-array-method-boxes-properly": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-get-iterator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", + "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.0", + "has-symbols": "^1.0.1", + "is-arguments": "^1.1.0", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "es-module-lexer": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.7.1.tgz", + "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==", + "dev": true + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.61", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", + "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", + "dev": true, + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + } + }, + "es5-shim": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.7.tgz", + "integrity": "sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==", + "dev": true + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + }, + "dependencies": { + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + } + } + }, + "es6-shim": { + "version": "0.35.6", + "resolved": "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.6.tgz", + "integrity": "sha512-EmTr31wppcaIAgblChZiuN/l9Y7DPyw8Xtbg7fIVngn6zMW+IEBJDJngeKC3x6wr0V/vcA2wqeFnaw1bFJbDdA==", "dev": true }, - "bootstrap": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz", - "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==" + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "esbuild-android-arm64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.8.tgz", + "integrity": "sha512-AilbChndywpk7CdKkNSZ9klxl+9MboLctXd9LwLo3b0dawmOF/i/t2U5d8LM6SbT1Xw36F8yngSUPrd8yPs2RA==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.8.tgz", + "integrity": "sha512-b6sdiT84zV5LVaoF+UoMVGJzR/iE2vNUfUDfFQGrm4LBwM/PWXweKpuu6RD9mcyCq18cLxkP6w/LD/w9DtX3ng==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.8.tgz", + "integrity": "sha512-R8YuPiiJayuJJRUBG4H0VwkEKo6AvhJs2m7Tl0JaIer3u1FHHXwGhMxjJDmK+kXwTFPriSysPvcobXC/UrrZCQ==", + "dev": true, + "optional": true }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "esbuild-freebsd-64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.8.tgz", + "integrity": "sha512-zBn6urrn8FnKC+YSgDxdof9jhPCeU8kR/qaamlV4gI8R3KUaUK162WYM7UyFVAlj9N0MyD3AtB+hltzu4cysTw==", "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } + "optional": true }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "esbuild-freebsd-arm64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.8.tgz", + "integrity": "sha512-pWW2slN7lGlkx0MOEBoUGwRX5UgSCLq3dy2c8RIOpiHtA87xAUpDBvZK10MykbT+aMfXc0NI2lu1X+6kI34xng==", "dev": true, - "requires": { - "fill-range": "^7.0.1" - } + "optional": true }, - "browserslist": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.18.1.tgz", - "integrity": "sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ==", + "esbuild-linux-32": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.8.tgz", + "integrity": "sha512-T0I0ueeKVO/Is0CAeSEOG9s2jeNNb8jrrMwG9QBIm3UU18MRB60ERgkS2uV3fZ1vP2F8i3Z2e3Zju4lg9dhVmw==", "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001280", - "electron-to-chromium": "^1.3.896", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" - } + "optional": true }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "esbuild-linux-64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.8.tgz", + "integrity": "sha512-Bm8SYmFtvfDCIu9sjKppFXzRXn2BVpuCinU1ChTuMtdKI/7aPpXIrkqBNOgPTOQO9AylJJc1Zw6EvtKORhn64w==", "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } + "optional": true }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "esbuild-linux-arm": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.8.tgz", + "integrity": "sha512-4/HfcC40LJ4GPyboHA+db0jpFarTB628D1ifU+/5bunIgY+t6mHkJWyxWxAAE8wl/ZIuRYB9RJFdYpu1AXGPdg==", + "dev": true, + "optional": true }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true + "esbuild-linux-arm64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.8.tgz", + "integrity": "sha512-X4pWZ+SL+FJ09chWFgRNO3F+YtvAQRcWh0uxKqZSWKiWodAB20flsW/OWFYLXBKiVCTeoGMvENZS/GeVac7+tQ==", + "dev": true, + "optional": true }, - "builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", - "dev": true + "esbuild-linux-mips64le": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.8.tgz", + "integrity": "sha512-o7e0D+sqHKT31v+mwFircJFjwSKVd2nbkHEn4l9xQ1hLR+Bv8rnt3HqlblY3+sBdlrOTGSwz0ReROlKUMJyldA==", + "dev": true, + "optional": true }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true + "esbuild-linux-ppc64le": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.8.tgz", + "integrity": "sha512-eZSQ0ERsWkukJp2px/UWJHVNuy0lMoz/HZcRWAbB6reoaBw7S9vMzYNUnflfL3XA6WDs+dZn3ekHE4Y2uWLGig==", + "dev": true, + "optional": true }, - "cacache": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.2.0.tgz", - "integrity": "sha512-uKoJSHmnrqXgthDFx/IU6ED/5xd+NNGe+Bb+kLZy7Ku4P+BaiWEUflAKPZ7eAzsYGcsAGASJZsybXp+quEcHTw==", + "esbuild-netbsd-64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.8.tgz", + "integrity": "sha512-gZX4kP7gVvOrvX0ZwgHmbuHczQUwqYppxqtoyC7VNd80t5nBHOFXVhWo2Ad/Lms0E8b+wwgI/WjZFTCpUHOg9Q==", "dev": true, - "requires": { - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - } + "optional": true }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "esbuild-openbsd-64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.8.tgz", + "integrity": "sha512-afzza308X4WmcebexbTzAgfEWt9MUkdTvwIa8xOu4CM2qGbl2LanqEl8/LUs8jh6Gqw6WsicEK52GPrS9wvkcw==", "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } + "optional": true }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "esbuild-sunos-64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.8.tgz", + "integrity": "sha512-mWPZibmBbuMKD+LDN23LGcOZ2EawMYBONMXXHmbuxeT0XxCNwadbCVwUQ/2p5Dp5Kvf6mhrlIffcnWOiCBpiVw==", "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } + "optional": true }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "esbuild-wasm": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.13.8.tgz", + "integrity": "sha512-UbD+3nloiSpJWXTCInZQrqPe8Y+RLfDkY/5kEHiXsw/lmaEvibe69qTzQu16m5R9je/0bF7VYQ5jaEOq0z9lLA==", "dev": true }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "esbuild-windows-32": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.8.tgz", + "integrity": "sha512-QsZ1HnWIcnIEApETZWw8HlOhDSWqdZX2SylU7IzGxOYyVcX7QI06ety/aDcn437mwyO7Ph4RrbhB+2ntM8kX8A==", + "dev": true, + "optional": true }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "esbuild-windows-64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.8.tgz", + "integrity": "sha512-76Fb57B9eE/JmJi1QmUW0tRLQZfGo0it+JeYoCDTSlbTn7LV44ecOHIMJSSgZADUtRMWT9z0Kz186bnaB3amSg==", "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } + "optional": true }, - "caniuse-lite": { - "version": "1.0.30001283", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001283.tgz", - "integrity": "sha512-9RoKo841j1GQFSJz/nCXOj0sD7tHBtlowjYlrqIUS812x9/emfBLBt6IyMz1zIaYc/eRL8Cs6HPUVi2Hzq4sIg==", + "esbuild-windows-arm64": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.8.tgz", + "integrity": "sha512-HW6Mtq5eTudllxY2YgT62MrVcn7oq2o8TAoAvDUhyiEmRmDY8tPwAhb1vxw5/cdkbukM3KdMYtksnUhF/ekWeg==", + "dev": true, + "optional": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, - "canonical-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz", - "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==", + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", "dev": true }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } } }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -3231,3028 +13866,3661 @@ "requires": { "is-glob": "^4.0.1" } - } - } - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "circular-dependency-plugin": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz", - "integrity": "sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + }, + "globals": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", + "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "type-fest": "^0.20.2" } - } - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-spinners": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", - "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", - "dev": true - }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "eslint-plugin-storybook": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.5.12.tgz", + "integrity": "sha512-ojuNKnrZFrQpm5N5Lp8UR0VEn4HtLjlNn6nxQAYlmTsEXNigtId1XPuMbXAsvFcEmv3RTb5l+9tZgkhSURfACg==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "@storybook/csf": "^0.0.1", + "@typescript-eslint/experimental-utils": "^5.3.0", + "requireindex": "^1.1.0" }, "dependencies": { - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "@storybook/csf": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.1.tgz", + "integrity": "sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "lodash": "^4.17.15" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.26.0.tgz", + "integrity": "sha512-OgUGXC/teXD8PYOkn33RSwBJPVwL0I2ipm5OHr9g9cfAhVrPC2DxQiWqaq88MNO5mbr/ZWnav3EVBpuwDreS5Q==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "5.26.0" } } } }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "color-name": "1.1.3" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colord": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.1.tgz", - "integrity": "sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw==", - "dev": true - }, - "colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "requires": { - "delayed-stream": "~1.0.0" + "eslint-visitor-keys": "^2.0.0" } }, - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true } } }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" + "estraverse": "^5.1.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } }, - "connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } } }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "estree-is-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-is-function/-/estree-is-function-1.0.0.tgz", + "integrity": "sha512-nSCWn1jkSq2QAtkaVLJZY2ezwcFO161HVc174zL1KPW3RJ+O6C3eJb8Nx7OXzvhoEv+nLgSR1g71oWUHUDTrJA==", "dev": true }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "copy-anything": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.3.tgz", - "integrity": "sha512-GK6QUtisv4fNS+XcI7shX0Gx9ORg7QqIznyfho79JTnX1XhLiyZHfftvGiziqzRiEi/Bjhgpi+D2o7HxJFPnDQ==", + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "dev": true, "requires": { - "is-what": "^3.12.0" + "d": "1", + "es5-ext": "~0.10.14" } }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "copy-webpack-plugin": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-9.0.1.tgz", - "integrity": "sha512-14gHKKdYIxF84jCEgPgYXCPpldbwpxxLbCmA7LReY7gvbaT555DgeBWBgBZM116tv/fO6RRJrsivBqRyRlukhw==", + "event-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", + "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==", "dev": true, "requires": { - "fast-glob": "^3.2.5", - "glob-parent": "^6.0.0", - "globby": "^11.0.3", - "normalize-path": "^3.0.0", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^6.0.0" - }, - "dependencies": { - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - } + "duplexer": "^0.1.1", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" } }, - "core-js": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.0.tgz", - "integrity": "sha512-5+5VxRFmSf97nM8Jr2wzOwLqRo6zphH2aX+7KsAUONObyzakDNq2G/bgbhinxB4PoV9L3aXQYhiDKyIKWd2c8g==", + "eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", "dev": true }, - "core-js-compat": { - "version": "3.19.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.19.2.tgz", - "integrity": "sha512-ObBY1W5vx/LFFMaL1P5Udo4Npib6fu+cMokeziWkA8Tns4FcDemKF5j9JvaI5JhdkW8EQJQGJN1EcrzmEwuAqQ==", + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "eventsource": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz", + "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==", "dev": true, "requires": { - "browserslist": "^4.18.1", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } + "original": "^1.0.0" } }, - "core-util-is": { + "evp_bytestokey": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { - "object-assign": "^4", - "vary": "^1" + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" } }, - "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "exec-sh": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", + "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "@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" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, - "critters": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.12.tgz", - "integrity": "sha512-ujxKtKc/mWpjrOKeaACTaQ1aP0O31M0ZPWhfl85jZF1smPU4Ivb9va5Ox2poif4zVJQQo0LCFlzGtEZAsCAPcw==", + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "chalk": "^4.1.0", - "css-select": "^4.1.3", - "parse5": "^6.0.1", - "parse5-htmlparser2-tree-adapter": "^6.0.1", - "postcss": "^8.3.7", - "pretty-bytes": "^5.3.0" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "ms": "2.0.0" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "is-descriptor": "^0.1.0" } }, - "color-convert": { + "extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "color-name": "~1.1.4" + "is-extendable": "^0.1.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "export-nehuba": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/export-nehuba/-/export-nehuba-0.0.12.tgz", + "integrity": "sha512-pf3hAwpXaOqlfBfgmPLYQ+uLqJ+ElyvE1bDrrCrf5Qf0Otsekw+8CcyAJhP5O15Yacmhe7Py3G96tw5bbvZyIA==", + "requires": { + "pako": "^1.0.6" + } + }, + "express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true } } }, - "css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "ext": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", "dev": true, "requires": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "type": "^2.5.0" + }, + "dependencies": { + "type": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", + "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==", "dev": true } } }, - "css-blank-pseudo": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", - "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "postcss": "^7.0.5" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" }, "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" + "is-plain-object": "^2.0.4" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, - "css-declaration-sorter": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.1.3.tgz", - "integrity": "sha512-SvjQjNRZgh4ULK1LDJ2AduPKUKxIqmtU7ZAyi47BTV+M90Qvxr9AB6lKlLbDUfXqI9IQeYA8LbAsCZPpJEV3aA==", + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, "requires": { - "timsort": "^0.3.0" + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" } }, - "css-has-pseudo": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", - "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^5.0.0-rc.4" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", - "dev": true + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } }, - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" + "kind-of": "^6.0.0" } }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "kind-of": "^6.0.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } } } }, - "css-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.2.0.tgz", - "integrity": "sha512-/rvHfYRjIpymZblf49w8jYcRo2y9gj6rV8UroHGmBxKrIyGLokpycyKzp9OkitvqT29ZSpzJ0Ic7SpnJX3sC8g==", + "fancy-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-2.0.0.tgz", + "integrity": "sha512-9CzxZbACXMUXW13tS0tI8XsGGmxWzO2DmYrGuBJOJ8k8q2K7hwfJA5qHjuPPe8wtsco33YR9wc+Rlr5wYFvhSA==", "dev": true, "requires": { - "icss-utils": "^5.1.0", - "postcss": "^8.2.15", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.1.0", - "semver": "^7.3.5" + "color-support": "^1.1.3" } }, - "css-minimizer-webpack-plugin": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.0.2.tgz", - "integrity": "sha512-B3I5e17RwvKPJwsxjjWcdgpU/zqylzK1bPVghcmpFHRL48DXiBgrtqz1BJsn68+t/zzaLp9kYAaEDvQ7GyanFQ==", + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, "requires": { - "cssnano": "^5.0.6", - "jest-worker": "^27.0.2", - "p-limit": "^3.0.2", - "postcss": "^8.3.5", - "schema-utils": "^3.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, "dependencies": { - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { - "yocto-queue": "^0.1.0" + "is-glob": "^4.0.1" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, - "css-parse": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", - "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "dev": true, + "requires": { + "format": "^0.2.0" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fetch-retry": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-5.0.2.tgz", + "integrity": "sha512-57Hmu+1kc6pKFUGVIobT7qw3NeAzY/uNN26bSevERLVvf6VGFR/ooDCOFBHMNDgAxBiU2YJq1D0vFzc6U1DcPw==", + "dev": true + }, + "figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + } + }, + "file-system-cache": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/file-system-cache/-/file-system-cache-1.1.0.tgz", + "integrity": "sha512-IzF5MBq+5CR0jXx5RxPe4BICl/oEhBSXKaL9fLhAXrIfIUS77Hr4vzrYyqYMHN6uTt+BOqi3fDCTjjEBCjERKw==", "dev": true, "requires": { - "css": "^2.0.0" + "fs-extra": "^10.1.0", + "ramda": "^0.28.0" } }, - "css-prefers-color-scheme": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", - "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "postcss": "^7.0.5" + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" }, "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" + "ms": "2.0.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } } }, - "css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", "dev": true, "requires": { - "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" } }, - "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "css-what": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", - "integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==", + "findit2": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", + "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=", "dev": true }, - "cssdb": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", - "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==", + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "flatten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", + "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==", "dev": true }, - "cssnano": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.0.12.tgz", - "integrity": "sha512-U38V4x2iJ3ijPdeWqUrEr4eKBB5PbEKsNP5T8xcik2Au3LeMtiMHX0i2Hu9k51FcKofNZumbrcdC6+a521IUHg==", + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", "dev": true, "requires": { - "cssnano-preset-default": "^5.1.8", - "is-resolvable": "^1.1.0", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - } - }, - "cssnano-preset-default": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.1.8.tgz", - "integrity": "sha512-zWMlP0+AMPBVE852SqTrP0DnhTcTA2C1wAF92TKZ3Va+aUVqLIhkqKlnJIXXdqXD7RN+S1ujuWmNpvrJBiM/vg==", - "dev": true, - "requires": { - "css-declaration-sorter": "^6.0.3", - "cssnano-utils": "^2.0.1", - "postcss-calc": "^8.0.0", - "postcss-colormin": "^5.2.1", - "postcss-convert-values": "^5.0.2", - "postcss-discard-comments": "^5.0.1", - "postcss-discard-duplicates": "^5.0.1", - "postcss-discard-empty": "^5.0.1", - "postcss-discard-overridden": "^5.0.1", - "postcss-merge-longhand": "^5.0.4", - "postcss-merge-rules": "^5.0.3", - "postcss-minify-font-values": "^5.0.1", - "postcss-minify-gradients": "^5.0.3", - "postcss-minify-params": "^5.0.2", - "postcss-minify-selectors": "^5.1.0", - "postcss-normalize-charset": "^5.0.1", - "postcss-normalize-display-values": "^5.0.1", - "postcss-normalize-positions": "^5.0.1", - "postcss-normalize-repeat-style": "^5.0.1", - "postcss-normalize-string": "^5.0.1", - "postcss-normalize-timing-functions": "^5.0.1", - "postcss-normalize-unicode": "^5.0.1", - "postcss-normalize-url": "^5.0.3", - "postcss-normalize-whitespace": "^5.0.1", - "postcss-ordered-values": "^5.0.2", - "postcss-reduce-initial": "^5.0.2", - "postcss-reduce-transforms": "^5.0.1", - "postcss-svgo": "^5.0.3", - "postcss-unique-selectors": "^5.0.2" + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" } }, - "cssnano-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz", - "integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==", - "dev": true - }, - "csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "focus-lock": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.8.1.tgz", + "integrity": "sha512-/LFZOIo82WDsyyv7h7oc0MJF9ACOvDRdx9rWPZ2pgMfNWu/z8hQDBtOchuB/0BVLmuFOZjV02YwUVzNsWx/EzA==", "dev": true, "requires": { - "css-tree": "^1.1.2" + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "follow-redirects": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz", + "integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "fork-ts-checker-webpack-plugin": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz", + "integrity": "sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==", "dev": true, "requires": { - "assert-plus": "^1.0.0" + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + } } }, - "date-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz", - "integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==", - "dev": true - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", "dev": true, "requires": { - "ms": "2.1.2" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=", "dev": true }, - "decode-uri-component": { + "forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" + "map-cache": "^0.2.2" } }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, - "default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" } }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "requires": { - "clone": "^1.0.2" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" } }, - "define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "requires": { - "object-keys": "^1.0.12" + "minipass": "^3.0.0" } }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } + "fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true }, - "del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "dev": true, "requires": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - }, - "dependencies": { - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" } }, - "delayed-stream": { + "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "dependency-graph": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", - "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, - "detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "get-assigned-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", + "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==", "dev": true }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "dev": true, "requires": { - "path-type": "^4.0.0" + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" } }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, - "dns-packet": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", - "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true, + "optional": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" + "pump": "^3.0.0" } }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", "dev": true, "requires": { - "buffer-indexof": "^1.0.0" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" } }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "github-slugger": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", + "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==", + "dev": true + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "dev": true, "requires": { - "esutils": "^2.0.2" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" + "is-glob": "^4.0.3" } }, - "dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "glob-promise": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", + "integrity": "sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw==", "dev": true, "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" + "@types/glob": "*" } }, - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, - "domhandler": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz", - "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==", + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", "dev": true, "requires": { - "domelementtype": "^2.2.0" + "min-document": "^2.19.0", + "process": "^0.11.10" } }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", "dev": true, "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" + "define-properties": "^1.1.3" } }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" } }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, - "electron-to-chromium": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.5.tgz", - "integrity": "sha512-YKaB+t8ul5crdh6OeqT2qXdxJGI0fAYb6/X8pDIyye+c3a7ndOCk5gVeKX+ABwivCGNS56vOAif3TN0qJMpEHw==", + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } }, - "encodeurl": { + "has-bigints": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true }, - "encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-glob/-/has-glob-1.0.0.tgz", + "integrity": "sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc=", "dev": true, - "optional": true, "requires": { - "iconv-lite": "^0.6.2" + "is-glob": "^3.0.0" }, "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, - "optional": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "is-extglob": "^2.1.0" } } } }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", "dev": true, "requires": { - "once": "^1.4.0" + "get-intrinsic": "^1.1.1" } }, - "engine.io": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", - "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "dev": true, "requires": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" - }, - "dependencies": { - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true - }, - "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true - } + "has-symbols": "^1.0.2" } }, - "engine.io-parser": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", - "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { - "@socket.io/base64-arraybuffer": "~1.0.2" + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" } }, - "enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "dev": true, "requires": { - "ansi-colors": "^4.1.1" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true - }, - "env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true - }, - "err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "dev": true, "requires": { - "prr": "~1.0.1" + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" } }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "hast-to-hyperscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz", + "integrity": "sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA==", "dev": true, "requires": { - "is-arrayish": "^0.2.1" + "@types/unist": "^2.0.3", + "comma-separated-tokens": "^1.0.0", + "property-information": "^5.3.0", + "space-separated-tokens": "^1.0.0", + "style-to-object": "^0.3.0", + "unist-util-is": "^4.0.0", + "web-namespaces": "^1.0.0" } }, - "es-module-lexer": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.7.1.tgz", - "integrity": "sha512-MgtWFl5No+4S3TmhDmCz2ObFGm6lEpTnzbQi+Dd+pw4mlTIZTmM2iAs5gRlmx5zS9luzobCSBSI90JM/1/JgOw==", - "dev": true - }, - "esbuild-android-arm64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.8.tgz", - "integrity": "sha512-AilbChndywpk7CdKkNSZ9klxl+9MboLctXd9LwLo3b0dawmOF/i/t2U5d8LM6SbT1Xw36F8yngSUPrd8yPs2RA==", - "dev": true, - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.8.tgz", - "integrity": "sha512-b6sdiT84zV5LVaoF+UoMVGJzR/iE2vNUfUDfFQGrm4LBwM/PWXweKpuu6RD9mcyCq18cLxkP6w/LD/w9DtX3ng==", - "dev": true, - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.8.tgz", - "integrity": "sha512-R8YuPiiJayuJJRUBG4H0VwkEKo6AvhJs2m7Tl0JaIer3u1FHHXwGhMxjJDmK+kXwTFPriSysPvcobXC/UrrZCQ==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.8.tgz", - "integrity": "sha512-zBn6urrn8FnKC+YSgDxdof9jhPCeU8kR/qaamlV4gI8R3KUaUK162WYM7UyFVAlj9N0MyD3AtB+hltzu4cysTw==", - "dev": true, - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.8.tgz", - "integrity": "sha512-pWW2slN7lGlkx0MOEBoUGwRX5UgSCLq3dy2c8RIOpiHtA87xAUpDBvZK10MykbT+aMfXc0NI2lu1X+6kI34xng==", - "dev": true, - "optional": true - }, - "esbuild-linux-32": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.8.tgz", - "integrity": "sha512-T0I0ueeKVO/Is0CAeSEOG9s2jeNNb8jrrMwG9QBIm3UU18MRB60ERgkS2uV3fZ1vP2F8i3Z2e3Zju4lg9dhVmw==", - "dev": true, - "optional": true - }, - "esbuild-linux-64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.8.tgz", - "integrity": "sha512-Bm8SYmFtvfDCIu9sjKppFXzRXn2BVpuCinU1ChTuMtdKI/7aPpXIrkqBNOgPTOQO9AylJJc1Zw6EvtKORhn64w==", + "hast-util-from-parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz", + "integrity": "sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA==", "dev": true, - "optional": true + "requires": { + "@types/parse5": "^5.0.0", + "hastscript": "^6.0.0", + "property-information": "^5.0.0", + "vfile": "^4.0.0", + "vfile-location": "^3.2.0", + "web-namespaces": "^1.0.0" + } }, - "esbuild-linux-arm": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.8.tgz", - "integrity": "sha512-4/HfcC40LJ4GPyboHA+db0jpFarTB628D1ifU+/5bunIgY+t6mHkJWyxWxAAE8wl/ZIuRYB9RJFdYpu1AXGPdg==", - "dev": true, - "optional": true + "hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "dev": true }, - "esbuild-linux-arm64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.8.tgz", - "integrity": "sha512-X4pWZ+SL+FJ09chWFgRNO3F+YtvAQRcWh0uxKqZSWKiWodAB20flsW/OWFYLXBKiVCTeoGMvENZS/GeVac7+tQ==", - "dev": true, - "optional": true + "hast-util-raw": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-6.0.1.tgz", + "integrity": "sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^6.0.0", + "hast-util-to-parse5": "^6.0.0", + "html-void-elements": "^1.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^3.0.0", + "vfile": "^4.0.0", + "web-namespaces": "^1.0.0", + "xtend": "^4.0.0", + "zwitch": "^1.0.0" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + } + } }, - "esbuild-linux-mips64le": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.8.tgz", - "integrity": "sha512-o7e0D+sqHKT31v+mwFircJFjwSKVd2nbkHEn4l9xQ1hLR+Bv8rnt3HqlblY3+sBdlrOTGSwz0ReROlKUMJyldA==", + "hast-util-to-parse5": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz", + "integrity": "sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==", "dev": true, - "optional": true + "requires": { + "hast-to-hyperscript": "^9.0.0", + "property-information": "^5.0.0", + "web-namespaces": "^1.0.0", + "xtend": "^4.0.0", + "zwitch": "^1.0.0" + } }, - "esbuild-linux-ppc64le": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.8.tgz", - "integrity": "sha512-eZSQ0ERsWkukJp2px/UWJHVNuy0lMoz/HZcRWAbB6reoaBw7S9vMzYNUnflfL3XA6WDs+dZn3ekHE4Y2uWLGig==", + "hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", "dev": true, - "optional": true + "requires": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + } }, - "esbuild-netbsd-64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.8.tgz", - "integrity": "sha512-gZX4kP7gVvOrvX0ZwgHmbuHczQUwqYppxqtoyC7VNd80t5nBHOFXVhWo2Ad/Lms0E8b+wwgI/WjZFTCpUHOg9Q==", + "hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", "dev": true, - "optional": true + "requires": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } }, - "esbuild-openbsd-64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.8.tgz", - "integrity": "sha512-afzza308X4WmcebexbTzAgfEWt9MUkdTvwIa8xOu4CM2qGbl2LanqEl8/LUs8jh6Gqw6WsicEK52GPrS9wvkcw==", - "dev": true, - "optional": true + "hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true }, - "esbuild-sunos-64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.8.tgz", - "integrity": "sha512-mWPZibmBbuMKD+LDN23LGcOZ2EawMYBONMXXHmbuxeT0XxCNwadbCVwUQ/2p5Dp5Kvf6mhrlIffcnWOiCBpiVw==", - "dev": true, - "optional": true + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true }, - "esbuild-wasm": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.13.8.tgz", - "integrity": "sha512-UbD+3nloiSpJWXTCInZQrqPe8Y+RLfDkY/5kEHiXsw/lmaEvibe69qTzQu16m5R9je/0bF7VYQ5jaEOq0z9lLA==", + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true }, - "esbuild-windows-32": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.8.tgz", - "integrity": "sha512-QsZ1HnWIcnIEApETZWw8HlOhDSWqdZX2SylU7IzGxOYyVcX7QI06ety/aDcn437mwyO7Ph4RrbhB+2ntM8kX8A==", + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, - "optional": true + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } }, - "esbuild-windows-64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.8.tgz", - "integrity": "sha512-76Fb57B9eE/JmJi1QmUW0tRLQZfGo0it+JeYoCDTSlbTn7LV44ecOHIMJSSgZADUtRMWT9z0Kz186bnaB3amSg==", + "hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, - "optional": true + "requires": { + "lru-cache": "^6.0.0" + } }, - "esbuild-windows-arm64": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.8.tgz", - "integrity": "sha512-HW6Mtq5eTudllxY2YgT62MrVcn7oq2o8TAoAvDUhyiEmRmDY8tPwAhb1vxw5/cdkbukM3KdMYtksnUhF/ekWeg==", + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", "dev": true, - "optional": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "html-entities": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", + "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", "dev": true }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "html-minifier-terser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "dev": true, + "requires": { + "camel-case": "^4.1.1", + "clean-css": "^4.2.3", + "commander": "^4.1.1", + "he": "^1.2.0", + "param-case": "^3.0.3", + "relateurl": "^0.2.7", + "terser": "^4.6.3" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + } + } + }, + "html-void-elements": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", + "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==", + "dev": true + }, + "html-webpack-plugin": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz", + "integrity": "sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A==", + "dev": true, + "requires": { + "@types/html-minifier-terser": "^5.0.0", + "@types/tapable": "^1.0.5", + "@types/webpack": "^4.41.8", + "html-minifier-terser": "^5.0.1", + "loader-utils": "^1.2.3", + "lodash": "^4.17.20", + "pretty-error": "^2.1.1", + "tapable": "^1.1.3", + "util.promisify": "1.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "minimist": "^1.2.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "dev": true, "requires": { - "color-name": "~1.1.4" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + } + } + }, + "htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + }, + "dependencies": { + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" } }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "domelementtype": "^2.3.0" } }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", "dev": true, "requires": { - "is-glob": "^4.0.1" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" } }, - "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "entities": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.0.tgz", + "integrity": "sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg==", + "dev": true + } + } + }, + "http-auth": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-4.1.9.tgz", + "integrity": "sha512-kvPYxNGc9EKGTXvOMnTBQw2RZfuiSihK/mLw/a4pbtRueTE45S55Lw/3k5CktIf7Ak0veMKEIteDj4YkNmCzmQ==", + "dev": true, + "requires": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.4.3", + "uuid": "^8.3.2" + } + }, + "http-auth-connect": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/http-auth-connect/-/http-auth-connect-1.0.5.tgz", + "integrity": "sha512-zykAOKpVAXyzhOLm6+xyB/RtRcfN3uDfH4Al73DIfeSb6B7nr0WToLI6UyyM6ohtcLmbBPksWXzVbEDStz8ObQ==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-parser-js": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", + "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==", + "dev": true + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "type-fest": "^0.20.2" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "has-flag": { + "fill-range": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "shebang-regex": "^3.0.0" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "shebang-regex": { + "is-number": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "ansi-regex": "^5.0.1" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "isexe": "^2.0.0" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } } } }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "dev": true, "requires": { - "eslint-visitor-keys": "^2.0.0" + "agent-base": "6", + "debug": "4" } }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", "dev": true, "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "ms": "^2.0.0" } }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "i18next": { + "version": "21.8.4", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.8.4.tgz", + "integrity": "sha512-b3LQ5n9V1juu8UItb5x1QTI4OTvNqsNs/wetwQlBvfijEqks+N5HKMKSoevf8w0/RGUrDQ7g4cvVzF8WBp9pUw==", "dev": true, "requires": { - "estraverse": "^5.1.0" + "@babel/runtime": "^7.17.2" }, "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true + "@babel/runtime": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.0.tgz", + "integrity": "sha512-YMQvx/6nKEaucl0MY56mwIG483xk8SDNdlUwb2Ts6FUpr7fm85DxEmsY18LXBNhcTz6tO6JwZV8w1W06v8UKeg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } } } }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } + "safer-buffer": ">= 2.1.2 < 3" } }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "dev": true }, - "eventemitter-asyncresource": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", - "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, - "eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", + "ignore-walk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-4.0.1.tgz", + "integrity": "sha512-rzDQLaW4jQbh2YrOFlJdCtX8qgJTehFRYiUB2r1osqTeDzV/3+Jh8fz1oAPzUThf3iku8Ds4IDqawI5d8mUiQw==", "dev": true, "requires": { - "original": "^1.0.0" + "minimatch": "^3.0.4" } }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } + "optional": true }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, - "export-nehuba": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/export-nehuba/-/export-nehuba-0.0.12.tgz", - "integrity": "sha512-pf3hAwpXaOqlfBfgmPLYQ+uLqJ+ElyvE1bDrrCrf5Qf0Otsekw+8CcyAJhP5O15Yacmhe7Py3G96tw5bbvZyIA==", + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "requires": { - "pako": "^1.0.6" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" } }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", "dev": true, "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" }, "dependencies": { - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "ms": "2.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "find-up": "^3.0.0" } } } }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" + "once": "^1.3.0", + "wrappy": "1" } }, - "extglob": { + "inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true + }, + "inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", + "dev": true + }, + "inquirer": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.1.2.tgz", + "integrity": "sha512-DHLKJwLPNgkfwNmsuEUKSejJFbkv0FMO9SMiQbjI3n5NQuCrSIBqP66ggqyz2a6t2qEolKrMjhQ3+W/xXgUQ+Q==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.3.0", + "run-async": "^2.4.0", + "rxjs": "^7.2.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "color-convert": "^2.0.1" } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "color-name": "~1.1.4" } }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "rxjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", + "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "tslib": "^2.1.0" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "has-flag": "^4.0.0" } } } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "inside": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/inside/-/inside-1.0.0.tgz", + "integrity": "sha1-20Xpk1c82z23C5gy6ChbrUZCR3A=", + "dev": true + }, + "internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "dev": true, + "requires": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true + }, + "ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true }, - "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "kind-of": "^3.0.2" }, "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-glob": "^4.0.1" + "is-buffer": "^1.1.5" } } } }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", "dev": true }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", "dev": true, "requires": { - "reusify": "^1.0.4" + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" } }, - "faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", "dev": true, "requires": { - "websocket-driver": ">=0.5.1" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "has-bigints": "^1.0.1" } }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { - "flat-cache": "^3.0.4" + "binary-extensions": "^2.0.0" } }, - "file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "requires": { - "to-regex-range": "^5.0.1" + "ci-info": "^2.0.0" } }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", "dev": true, "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "ms": "2.0.0" + "is-buffer": "^1.1.5" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + } + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true } } }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-dom": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-dom/-/is-dom-1.1.0.tgz", + "integrity": "sha512-u82f6mvhYxRPKpw8V1N0W8ce1xXwOrQtgGcxl6UCL5zBmZu3is/18K0rR7uFCnMDuAsS/3W54mGL4vsaFUQlEQ==", + "dev": true, + "requires": { + "is-object": "^1.0.1", + "is-window": "^1.0.2" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", + "dev": true + }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", "dev": true, "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "is-path-inside": "^2.1.0" } }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "path-is-inside": "^1.0.2" } }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "isobject": "^3.0.1" } }, - "flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true - }, - "flatten": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", - "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==", - "dev": true + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } }, - "follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", "dev": true }, - "for-in": { + "is-shared-array-buffer": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "has-tostringtag": "^1.0.0" } }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "requires": { - "map-cache": "^0.2.2" + "has-symbols": "^1.0.2" } }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, - "fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } + "optional": true }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, "requires": { - "minipass": "^3.0.0" + "call-bind": "^1.0.2" } }, - "fs-monkey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", - "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", "dev": true }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "is-whitespace-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true + "is-window": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz", + "integrity": "sha1-LIlspT25feRdPDMTOmXYyfVjSA0=", + "dev": true }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "is-word-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", "dev": true }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "is-docker": "^2.0.0" } }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", "dev": true }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", "dev": true, "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "dev": true, "requires": { - "pump": "^3.0.0" + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "assert-plus": "^1.0.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "istanbul-reports": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", "dev": true, "requires": { - "is-glob": "^4.0.3" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "iterate-iterator": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.2.tgz", + "integrity": "sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw==", "dev": true }, - "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", "dev": true, "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" } }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - }, - "handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "jasmine-core": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.8.0.tgz", + "integrity": "sha512-zl0nZWDrmbCiKns0NcjkFGYkVTGCPUgoHypTaj+G2AzaWus7QGoXARSlYsSle2VRpSdfJmM+hzmFKzQNhF2kHg==", "dev": true }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "jasmine-marbles": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/jasmine-marbles/-/jasmine-marbles-0.8.4.tgz", + "integrity": "sha512-zbtuXABpSWrSswYPiZ5m6EQhluNmKcRQs+82AqJHSN+PMx3ASpDmTvRfqe9Pk2hPh9Ge5zrzOsorIlw3kdwTXQ==", "dev": true, "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" + "lodash": "^4.17.20" } }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "jest-haste-map": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", + "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", "dev": true, "requires": { - "function-bind": "^1.1.1" + "@jest/types": "^26.6.2", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^26.0.0", + "jest-serializer": "^26.6.2", + "jest-util": "^26.6.2", + "jest-worker": "^26.6.2", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "jest-mock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", "dev": true, "requires": { - "has-symbols": "^1.0.2" + "@jest/types": "^27.5.1", + "@types/node": "*" + }, + "dependencies": { + "@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "jest-regex-util": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", "dev": true }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "jest-serializer": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", + "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", "dev": true, "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "@types/node": "*", + "graceful-fs": "^4.2.4" } }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "jest-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", + "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" }, "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" } }, - "kind-of": { + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "has-flag": "^4.0.0" } } } }, - "hbp-connectivity-component": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/hbp-connectivity-component/-/hbp-connectivity-component-0.5.2.tgz", - "integrity": "sha512-lGFkfuEWbuor9sqBnJxBcArdQ04WeHPvNAuHdaJL8bHMWxbAlnEOrPGn4AkdQO587rWD78cVw3LwEFSBm3O1Bg==", + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, "requires": { - "@stencil/core": "^1.16.5", - "@types/node": "^14.0.24", - "bootstrap": "^4.4.1", - "jszip": "^3.5.0" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "dependencies": { - "@types/node": { - "version": "14.17.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.34.tgz", - "integrity": "sha512-USUftMYpmuMzeWobskoPfzDi+vkpe0dvcOBRNOscFrGxVp4jomnRxWuVohgqBow2xyIPC0S3gjxV/5079jhmDg==" + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, - "hdr-histogram-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.1.tgz", - "integrity": "sha512-uPZxl1dAFnjUFHWLZmt93vUUvtHeaBay9nVNHu38SdOjMSF/4KqJUqa1Seuj08ptU1rEb6AHvB41X8n/zFZ74Q==", - "dev": true, - "requires": { - "@assemblyscript/loader": "^0.10.1", - "base64-js": "^1.2.0", - "pako": "^1.0.3" - } + "js-string-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", + "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=", + "dev": true }, - "hdr-histogram-percentiles-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", - "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "hosted-git-info": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", - "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { - "lru-cache": "^6.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true }, - "html-entities": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", - "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" + }, + "jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", "dev": true }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" } }, - "http-parser-js": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.5.tgz", - "integrity": "sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA==", + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, + "jszip": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz", + "integrity": "sha512-LDfVtOLtOxb9RXkYOwPyNBTQDL4eUbqahtoY6x07GiDJHwSYvn8sHHIw8wINImV3MqbMNve2gSuM1DDqEKk09Q==", "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" } }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } + "junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "dev": true }, - "http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "karma": { + "version": "6.3.20", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.20.tgz", + "integrity": "sha512-HRNQhMuKOwKpjYlWiJP0DUrJOh+QjaI/DTaD8b9rEm4Il3tJ8MijutVZH4ts10LuUFst/CedwTS6vieCN8yTSw==", "dev": true, "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" }, "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "minimist": "^1.2.6" } }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "rimraf": "^3.0.0" } }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true } } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "karma-chrome-launcher": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz", + "integrity": "sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==", "dev": true, "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "which": "^1.2.1" } }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "karma-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.0.3.tgz", + "integrity": "sha512-atDvLQqvPcLxhED0cmXYdsPMCQuh6Asa9FMZW1bhNqlVEhJoB9qyZ2BY1gu7D/rr5GLGb5QzYO4siQskxaWP/g==", "dev": true, "requires": { - "agent-base": "6", - "debug": "4" + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.1", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.0", + "minimatch": "^3.0.4" } }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "karma-jasmine": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.2.tgz", + "integrity": "sha512-ggi84RMNQffSDmWSyyt4zxzh2CQGwsxvYYsprgyR1j8ikzIduEdOlcLvXjZGwXG/0j41KUXOWsUCBfbEHPWP9g==", "dev": true, "requires": { - "ms": "^2.0.0" + "jasmine-core": "^3.6.0" } }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "karma-jasmine-html-reporter": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz", + "integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==", + "dev": true + }, + "karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "source-map-support": "^0.5.5" } }, - "icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", "dev": true }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, - "ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, - "ignore-walk": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", - "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", + "klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true + }, + "lazy-universal-dotenv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-3.0.1.tgz", + "integrity": "sha512-prXSYk799h3GY3iOWnC6ZigYzMPjxN2svgjJ9shk7oMadSNX3wXy0B6F32PMJv7qtMnrIbUxoEHzbutvxR2LBQ==", "dev": true, "requires": { - "minimatch": "^3.0.4" + "@babel/runtime": "^7.5.0", + "app-root-dir": "^1.0.2", + "core-js": "^3.0.4", + "dotenv": "^8.0.0", + "dotenv-expand": "^5.1.0" } }, - "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "less": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.1.tgz", + "integrity": "sha512-w09o8tZFPThBscl5d0Ggp3RcrKIouBoQscnOMgFH3n5V3kN/CXGHNfCkRPtxJk6nKryDXaV9aHLK55RXuH4sAw==", "dev": true, - "optional": true + "requires": { + "copy-anything": "^2.0.1", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^2.5.2", + "parse-node-version": "^1.0.1", + "source-map": "~0.6.0", + "tslib": "^1.10.0" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "optional": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + "less-loader": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-10.0.1.tgz", + "integrity": "sha512-Crln//HpW9M5CbtdfWm3IO66Cvx1WhZQvNybXgfB2dD/6Sav9ppw+IWqs/FQKPBFO4B6X0X28Z0WNznshgwUzA==", + "dev": true, + "requires": { + "klona": "^2.0.4" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "license-webpack-plugin": { + "version": "2.3.20", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.3.20.tgz", + "integrity": "sha512-AHVueg9clOKACSHkhmEI+PCC9x8+qsQVuKECZD3ETxETK5h/PCv5/MUzyG1gm8OMcip/s1tcNxqo9Qb7WhjGsg==", + "dev": true, + "requires": { + "@types/webpack-sources": "^0.1.5", + "webpack-sources": "^1.2.0" + } }, - "import-fresh": { + "lie": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "immediate": "~3.0.5" } }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "lilconfig": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", + "integrity": "sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, + "optional": true, "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" }, "dependencies": { - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, + "optional": true, "requires": { - "find-up": "^3.0.0" + "error-ex": "^1.2.0" } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "optional": true } } }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true }, - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, - "inquirer": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.1.2.tgz", - "integrity": "sha512-DHLKJwLPNgkfwNmsuEUKSejJFbkv0FMO9SMiQbjI3n5NQuCrSIBqP66ggqyz2a6t2qEolKrMjhQ3+W/xXgUQ+Q==", + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.3.0", - "run-async": "^2.4.0", - "rxjs": "^7.2.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6275,454 +17543,451 @@ "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "rxjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", - "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", - "dev": true, - "requires": { - "tslib": "~2.1.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", - "dev": true - } - } - }, - "internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dev": true, - "requires": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - } - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true - }, - "is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "log4js": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.5.1.tgz", + "integrity": "sha512-z1hRRe5DDPzsP73PgN/GYmeSbIAl/g9kX3GLjABCpcU1ojns8S4cyjpJ21jU1P7z1wWkm69PjyMcEGqYYdDqaA==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "date-format": "^4.0.10", + "debug": "^4.3.4", + "flatted": "^3.2.5", + "rfdc": "^1.3.0", + "streamroller": "^3.1.1" } }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "loglevel": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", + "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", "dev": true }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "requires": { - "binary-extensions": "^2.0.0" + "js-tokens": "^3.0.0 || ^4.0.0" } }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-core-module": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", - "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, + "optional": true, "requires": { - "has": "^1.0.3" + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "tslib": "^2.0.3" } }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", "dev": true, "requires": { - "has-tostringtag": "^1.0.0" + "fault": "^1.0.0", + "highlight.js": "~10.7.0" } }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "yallist": "^4.0.0" } }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", "dev": true }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", "dev": true }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "macos-release": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz", + "integrity": "sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g==", "dev": true }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "sourcemap-codec": "^1.4.4" } }, - "is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true - }, - "is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, - "is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "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==", "dev": true, "requires": { - "is-path-inside": "^2.1.0" + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", "dev": true, "requires": { - "path-is-inside": "^1.0.2" + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" } }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, "requires": { - "isobject": "^3.0.1" + "tmpl": "1.0.5" } }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "p-defer": "^1.0.0" } }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true, + "optional": true }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "map-or-similar": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", + "integrity": "sha1-beJlMXSt+12e3DPGnT6Sobdvrwg=", "dev": true }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", "dev": true }, - "is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "dev": true + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "markdown-escapes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", + "dev": true + }, + "marked": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.16.tgz", + "integrity": "sha512-wahonIQ5Jnyatt2fn8KqF/nIqZM8mh3oRu2+l5EANGMhu6RFjiSG52QNE2eWzFMI94HqYSgN184NurgNG6CztA==", "dev": true }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, "requires": { - "is-docker": "^2.0.0" + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "mdast-squeeze-paragraphs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", + "integrity": "sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==", + "dev": true, + "requires": { + "unist-util-remove": "^2.0.0" + } }, - "isbinaryfile": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", - "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", - "dev": true + "mdast-util-definitions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", + "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", + "dev": true, + "requires": { + "unist-util-visit": "^2.0.0" + } }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "mdast-util-to-hast": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz", + "integrity": "sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "mdast-util-definitions": "^4.0.0", + "mdurl": "^1.0.0", + "unist-builder": "^2.0.0", + "unist-util-generated": "^1.0.0", + "unist-util-position": "^3.0.0", + "unist-util-visit": "^2.0.0" + } + }, + "mdast-util-to-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", + "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==", "dev": true }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "dev": true }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", "dev": true }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "mem": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz", + "integrity": "sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==", "dev": true, "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" + "map-age-cleaner": "^0.1.3", + "mimic-fn": "^3.1.0" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", "dev": true } } }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "memfs": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", + "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", "dev": true, "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" + "fs-monkey": "1.0.3" + } + }, + "memoizerific": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", + "integrity": "sha1-fIekZGREwy11Q4VwkF8tvRsagFo=", + "dev": true, + "requires": { + "map-or-similar": "^1.5.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "optional": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" }, "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "optional": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, + "optional": true, "requires": { - "has-flag": "^4.0.0" + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "optional": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "optional": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "optional": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" } } } }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", "dev": true, "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" }, "dependencies": { @@ -6734,1940 +17999,2128 @@ } } }, - "istanbul-reports": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz", - "integrity": "sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, - "jasmine-core": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.8.0.tgz", - "integrity": "sha512-zl0nZWDrmbCiKns0NcjkFGYkVTGCPUgoHypTaj+G2AzaWus7QGoXARSlYsSle2VRpSdfJmM+hzmFKzQNhF2kHg==", + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, - "jasmine-marbles": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/jasmine-marbles/-/jasmine-marbles-0.8.4.tgz", - "integrity": "sha512-zbtuXABpSWrSswYPiZ5m6EQhluNmKcRQs+82AqJHSN+PMx3ASpDmTvRfqe9Pk2hPh9Ge5zrzOsorIlw3kdwTXQ==", + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "microevent.ts": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", + "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "lodash": "^4.17.20" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, - "jest-worker": { - "version": "27.4.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.1.tgz", - "integrity": "sha512-cWYUNkfST1i17513Ll3GM5h/lJtYR1hRZsfPL4OQkIBWgKj4kbJREgHhxJxZmOmjKyeAg02HRoMWXr+B/JtFgg==", + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" }, "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "mime-db": "1.52.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "dev": true, + "requires": { + "dom-walk": "^0.1.0" + } }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "mini-css-extract-plugin": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.4.2.tgz", + "integrity": "sha512-ZmqShkn79D36uerdED+9qdo1ZYG8C1YsWvXu0UMJxurZnSdgz7gQKO2EGv8T55MhDqG3DYmGtizZNpM/UbTlcA==", + "dev": true, + "requires": { + "schema-utils": "^3.1.0" + } }, - "json-stable-stringify-without-jsonify": { + "minimalistic-assert": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", "dev": true }, - "json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "minipass": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", + "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", + "dev": true, "requires": { - "minimist": "^1.2.5" + "yallist": "^4.0.0" } }, - "jsonc-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", - "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", - "dev": true + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", "dev": true, "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" + "encoding": "^0.1.12", + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" } }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", "dev": true, "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" } }, - "jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "minipass": "^3.0.0" } }, - "karma": { - "version": "6.3.16", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz", - "integrity": "sha512-nEU50jLvDe5yvXqkEJRf8IuvddUkOY2x5Xc4WXHz6dxINgGDrgD2uqQWeVrJs4hbfNaotn+HQ1LZJ4yOXrL7xQ==", + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, "requires": { - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "colors": "1.4.0", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.2.0", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "is-plain-object": "^2.0.4" } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "color-name": "~1.1.4" + "ms": "2.0.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", "dev": true, "requires": { - "minimist": "^1.2.5" + "ee-first": "1.1.1" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "minimist": "^1.2.6" } }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "glob": "^7.1.3" } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true } } }, - "karma-chrome-launcher": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", - "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", "dev": true, "requires": { - "which": "^1.2.1" + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" } }, - "karma-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.0.3.tgz", - "integrity": "sha512-atDvLQqvPcLxhED0cmXYdsPMCQuh6Asa9FMZW1bhNqlVEhJoB9qyZ2BY1gu7D/rr5GLGb5QzYO4siQskxaWP/g==", + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.1", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.0", - "minimatch": "^3.0.4" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" } }, - "karma-jasmine": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.1.tgz", - "integrity": "sha512-h8XDAhTiZjJKzfkoO1laMH+zfNlra+dEQHUAjpn5JV1zCPtOIVWGQjLBrqhnzQa/hrU2XrZwSyBa6XjEBzfXzw==", + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "needle": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", "dev": true, + "optional": true, "requires": { - "jasmine-core": "^3.6.0" + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + } } }, - "karma-jasmine-html-reporter": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz", - "integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==", + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true }, - "karma-source-map-support": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", - "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==", + "dev": true + }, + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + }, + "nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", "dev": true, + "optional": true, "requires": { - "source-map-support": "^0.5.5" + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" } }, - "killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } }, - "klona": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", - "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", "dev": true }, - "less": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/less/-/less-4.1.1.tgz", - "integrity": "sha512-w09o8tZFPThBscl5d0Ggp3RcrKIouBoQscnOMgFH3n5V3kN/CXGHNfCkRPtxJk6nKryDXaV9aHLK55RXuH4sAw==", + "node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", "dev": true, "requires": { - "copy-anything": "^2.0.1", - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", - "mime": "^1.4.1", - "needle": "^2.5.2", - "parse-node-version": "^1.0.1", - "source-map": "~0.6.0", - "tslib": "^1.10.0" + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" }, "dependencies": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "optional": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "isexe": "^2.0.0" + } + } + } + }, + "node-gyp-build": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", + "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", + "dev": true, + "optional": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "node-releases": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz", + "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==", + "dev": true + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "optional": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true } } }, - "less-loader": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-10.0.1.tgz", - "integrity": "sha512-Crln//HpW9M5CbtdfWm3IO66Cvx1WhZQvNybXgfB2dD/6Sav9ppw+IWqs/FQKPBFO4B6X0X28Z0WNznshgwUzA==", + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true + }, + "npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", "dev": true, "requires": { - "klona": "^2.0.4" + "npm-normalize-package-bin": "^1.0.1" } }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "npm-install-checks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz", + "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==", "dev": true, "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "semver": "^7.1.1" } }, - "license-webpack-plugin": { - "version": "2.3.20", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.3.20.tgz", - "integrity": "sha512-AHVueg9clOKACSHkhmEI+PCC9x8+qsQVuKECZD3ETxETK5h/PCv5/MUzyG1gm8OMcip/s1tcNxqo9Qb7WhjGsg==", + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "npm-package-arg": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz", + "integrity": "sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==", "dev": true, "requires": { - "@types/webpack-sources": "^0.1.5", - "webpack-sources": "^1.2.0" + "hosted-git-info": "^4.0.1", + "semver": "^7.3.4", + "validate-npm-package-name": "^3.0.0" } }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "npm-packlist": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-3.0.0.tgz", + "integrity": "sha512-L/cbzmutAwII5glUcf2DBRNY/d0TFd4e/FnaZigJV6JD85RHZXJFGwCndjMWiiViiWSsWt3tiOLpI3ByTnIdFQ==", + "dev": true, "requires": { - "immediate": "~3.0.5" + "glob": "^7.1.6", + "ignore-walk": "^4.0.1", + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" } }, - "lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", - "dev": true - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "npm-pick-manifest": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz", + "integrity": "sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA==", + "dev": true, + "requires": { + "npm-install-checks": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^8.1.2", + "semver": "^7.3.4" + } }, - "loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true + "npm-registry-fetch": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz", + "integrity": "sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA==", + "dev": true, + "requires": { + "make-fetch-happen": "^9.0.1", + "minipass": "^3.1.3", + "minipass-fetch": "^1.3.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.0.0", + "npm-package-arg": "^8.0.0" + } }, - "loader-utils": { + "npm-run-path": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" + "path-key": "^2.0.0" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" } }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", "dev": true }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "color-name": "~1.1.4" + "is-descriptor": "^0.1.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "has-flag": "^4.0.0" + "is-buffer": "^1.1.5" } } } }, - "log4js": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.1.tgz", - "integrity": "sha512-iUiYnXqAmNKiIZ1XSAitQ4TmNs8CdZYTAWINARF3LjnsLN8tY5m0vRwd6uuWj/yNY0YHxeZodnbmxKFUOM2rMg==", + "object-inspect": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.1.tgz", + "integrity": "sha512-Y/jF6vnvEtOPGiKD1+q+X0CiUYRQtEHp89MLLUJ7TUivtH8Ugn2+3A7Rynqk7BRsAoqeOQWnFnjpDrKSxDgIGA==", + "dev": true + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "date-format": "^4.0.3", - "debug": "^4.3.3", - "flatted": "^3.2.4", - "rfdc": "^1.3.0", - "streamroller": "^3.0.2" + "isobject": "^3.0.0" } }, - "loglevel": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", - "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", - "dev": true + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", "dev": true, "requires": { - "yallist": "^4.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" } }, - "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", "dev": true, "requires": { - "sourcemap-codec": "^1.4.4" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" } }, - "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==", + "object.getownpropertydescriptors": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", + "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", "dev": true, "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "array.prototype.reduce": "^1.0.4", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.1" } }, - "make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" + "isobject": "^3.0.1" } }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, "requires": { - "p-defer": "^1.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" } }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "requires": { - "object-visit": "^1.0.0" + "ee-first": "1.1.1" } }, - "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", "dev": true }, - "mem": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz", - "integrity": "sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==", + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "map-age-cleaner": "^0.1.3", - "mimic-fn": "^3.1.0" - }, - "dependencies": { - "mimic-fn": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", - "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", - "dev": true - } + "wrappy": "1" } }, - "memfs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.0.tgz", - "integrity": "sha512-o/RfP0J1d03YwsAxyHxAYs2kyJp55AFkMazlFAZFR2I2IXkxiUTXRabJ6RmNNCQ83LAD2jy52Khj0m3OffpNdA==", + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { - "fs-monkey": "1.0.3" + "mimic-fn": "^2.1.0" } }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "open": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/open/-/open-8.2.1.tgz", + "integrity": "sha512-rXILpcQlkF/QuFez2BJDf3GsqpjGKbkUUToAIGo9A0Q6ZkoSGogZJulrUdwRkrAsoQvoZsrjCYt8+zblOk7JQQ==", "dev": true, "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" } }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", "dev": true }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", "dev": true, "requires": { - "source-map": "^0.6.1" + "is-wsl": "^1.1.0" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true } } }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "url-parse": "^1.4.3" } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "optional": true }, - "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "os-name": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", + "integrity": "sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==", "dev": true, "requires": { - "mime-db": "1.51.0" + "macos-release": "^2.5.0", + "windows-release": "^4.0.0" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, - "mini-css-extract-plugin": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.4.2.tgz", - "integrity": "sha512-ZmqShkn79D36uerdED+9qdo1ZYG8C1YsWvXu0UMJxurZnSdgz7gQKO2EGv8T55MhDqG3DYmGtizZNpM/UbTlcA==", + "p-all": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-all/-/p-all-2.1.0.tgz", + "integrity": "sha512-HbZxz5FONzz/z2gJfk6bFca0BCiSRF8jU3yCsWOen/vR6lZjfPOu/e7L3uFzTW1i0H8TlC3vqQstEJPQL4/uLA==", "dev": true, "requires": { - "schema-utils": "^3.1.0" + "p-map": "^2.0.0" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } } }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "p-timeout": "^3.1.0" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "minipass": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", - "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", + "p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", "dev": true, "requires": { - "yallist": "^4.0.0" + "p-map": "^2.0.0" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } } }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true }, - "minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "encoding": "^0.1.12", - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" + "p-try": "^2.0.0" } }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "minipass": "^3.0.0" + "p-limit": "^2.2.0" } }, - "minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "requires": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" + "aggregate-error": "^3.0.0" } }, - "minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", "dev": true, "requires": { - "minipass": "^3.0.0" + "retry": "^0.12.0" } }, - "minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", "dev": true, "requires": { - "minipass": "^3.0.0" + "p-finally": "^1.0.0" } }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "pacote": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-12.0.2.tgz", + "integrity": "sha512-Ar3mhjcxhMzk+OVZ8pbnXdb0l8+pimvlsqBGRNkble2NVgyqOGE3yrCGi/lAYq7E7NRDMz89R1Wx5HIMCGgeYg==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } + "@npmcli/git": "^2.1.0", + "@npmcli/installed-package-contents": "^1.0.6", + "@npmcli/promise-spawn": "^1.2.0", + "@npmcli/run-script": "^2.0.0", + "cacache": "^15.0.5", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.3", + "mkdirp": "^1.0.3", + "npm-package-arg": "^8.0.1", + "npm-packlist": "^3.0.0", + "npm-pick-manifest": "^6.0.0", + "npm-registry-fetch": "^11.0.0", + "promise-retry": "^2.0.1", + "read-package-json-fast": "^2.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.1.0" } }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, - "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", "dev": true, "requires": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" } }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==" - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } }, - "needle": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", - "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "optional": true, "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - } + "callsites": "^3.0.0" } }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } }, - "nice-napi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", - "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, - "optional": true, "requires": { - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.2" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" } }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", "dev": true }, - "node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "optional": true }, - "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true + "parse5-html-rewriting-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-6.0.1.tgz", + "integrity": "sha512-vwLQzynJVEfUlURxgnf51yAJDQTtVpNyGD8tKi2Za7m+akukNHxCcUQMAa/mUGLhCeicFdpy7Tlvj8ZNKadprg==", + "dev": true, + "requires": { + "parse5": "^6.0.1", + "parse5-sax-parser": "^6.0.1" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + } + } }, - "node-gyp": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz", - "integrity": "sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==", + "parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", "dev": true, "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.3", - "nopt": "^5.0.0", - "npmlog": "^4.1.2", - "request": "^2.88.2", - "rimraf": "^3.0.2", - "semver": "^7.3.2", - "tar": "^6.0.2", - "which": "^2.0.2" + "parse5": "^6.0.1" }, "dependencies": { - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true } } }, - "node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "parse5-sax-parser": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", + "integrity": "sha512-kXX+5S81lgESA0LsDuGjAlBybImAChYRMT+/uKCEXFBFOeEhS52qUCydGhU3qLRD8D9DVjaUo821WK7DM4iCeg==", "dev": true, - "optional": true + "requires": { + "parse5": "^6.0.1" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + } + } }, - "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true }, - "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", "dev": true, "requires": { - "abbrev": "1" + "no-case": "^3.0.4", + "tslib": "^2.0.3" } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", "dev": true }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", "dev": true }, - "npm-bundled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", - "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", - "dev": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-install-checks": { + "path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz", - "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==", - "dev": true, - "requires": { - "semver": "^7.1.1" - } + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true }, - "npm-normalize-package-bin": { + "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "npm-package-arg": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz", - "integrity": "sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==", - "dev": true, - "requires": { - "hosted-git-info": "^4.0.1", - "semver": "^7.3.4", - "validate-npm-package-name": "^3.0.0" - } + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true }, - "npm-packlist": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.2.2.tgz", - "integrity": "sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg==", - "dev": true, - "requires": { - "glob": "^7.1.6", - "ignore-walk": "^3.0.3", - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" - } + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, - "npm-pick-manifest": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz", - "integrity": "sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA==", - "dev": true, - "requires": { - "npm-install-checks": "^4.0.0", - "npm-normalize-package-bin": "^1.0.1", - "npm-package-arg": "^8.1.2", - "semver": "^7.3.4" - } + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, - "npm-registry-fetch": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz", - "integrity": "sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA==", - "dev": true, - "requires": { - "make-fetch-happen": "^9.0.1", - "minipass": "^3.1.3", - "minipass-fetch": "^1.3.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.0.0", - "npm-package-arg": "^8.0.0" - } + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { - "path-key": "^2.0.0" + "through": "~2.3" } }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", "dev": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, - "nth-check": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", - "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", + "pdfjs-dist": { + "version": "2.14.305", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.14.305.tgz", + "integrity": "sha512-5f7i25J1dKIBczhgfxEgNxfYNIxXEdxqo6Qb4ehY7Ja+p6AI4uUmk/OcVGXfRGm2ys5iaJJhJUwBFwv6Jl/Qww==", "dev": true, "requires": { - "boolbase": "^1.0.0" + "dommatrix": "^1.0.1", + "web-streams-polyfill": "^3.2.1" } }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "pdfmake": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.2.5.tgz", + "integrity": "sha512-NlayjehMtuZEdw2Lyipf/MxOCR2vATZQ7jn8cH0/dHwsNb+mqof9/6SW4jZT5p+So4qz+0mD21KG81+dDQSEhA==", "dev": true, "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "@foliojs-fork/linebreak": "^1.1.1", + "@foliojs-fork/pdfkit": "^0.13.0", + "iconv-lite": "^0.6.3", + "xmldoc": "^1.1.2" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "safer-buffer": ">= 2.1.2 < 3.0.0" } } } }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", "dev": true }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "ee-first": "1.1.1" + "pinkie": "^2.0.0" } }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", "dev": true }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "piscina": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.1.0.tgz", + "integrity": "sha512-KTW4sjsCD34MHrUbx9eAAbuUSpVj407hQSgk/6Epkg0pbRBmv4a3UX7Sr8wxm9xYqQLnsN4mFOjqGDzHAdgKQg==", "dev": true, "requires": { - "wrappy": "1" + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0", + "nice-napi": "^1.0.2" } }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { - "mimic-fn": "^2.1.0" + "find-up": "^4.0.0" } }, - "open": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/open/-/open-8.2.1.tgz", - "integrity": "sha512-rXILpcQlkF/QuFez2BJDf3GsqpjGKbkUUToAIGo9A0Q6ZkoSGogZJulrUdwRkrAsoQvoZsrjCYt8+zblOk7JQQ==", + "png-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==", + "dev": true + }, + "pnp-webpack-plugin": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", + "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", "dev": true, "requires": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" + "ts-pnp": "^1.1.6" } }, - "opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "polished": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", + "integrity": "sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==", "dev": true, "requires": { - "is-wsl": "^1.1.0" + "@babel/runtime": "^7.17.8" }, "dependencies": { - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true + "@babel/runtime": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.0.tgz", + "integrity": "sha512-YMQvx/6nKEaucl0MY56mwIG483xk8SDNdlUwb2Ts6FUpr7fm85DxEmsY18LXBNhcTz6tO6JwZV8w1W06v8UKeg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } } } }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", "dev": true, "requires": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "ms": "^2.1.1" } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "ansi-regex": "^5.0.1" + "minimist": "^1.2.6" } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-attribute-case-insensitive": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz", + "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^6.0.2" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "picocolors": "^0.2.1", + "source-map": "^0.6.1" } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", "dev": true, "requires": { - "url-parse": "^1.4.3" + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" } }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "postcss-color-functional-notation": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", + "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", "dev": true, "requires": { - "p-try": "^2.0.0" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "postcss-color-gray": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", + "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "postcss-color-hex-alpha": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", + "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", "dev": true, "requires": { - "aggregate-error": "^3.0.0" + "postcss": "^7.0.14", + "postcss-values-parser": "^2.0.1" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "postcss-color-mod-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", + "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", "dev": true, "requires": { - "retry": "^0.12.0" + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pacote": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.3.5.tgz", - "integrity": "sha512-fT375Yczn4zi+6Hkk2TBe1x1sP8FgFsEIZ2/iWaXY2r/NkhDJfxbcn5paz1+RTFCyNf+dPnaoBDJoAxXSU8Bkg==", + "postcss-color-rebeccapurple": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", + "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", "dev": true, "requires": { - "@npmcli/git": "^2.1.0", - "@npmcli/installed-package-contents": "^1.0.6", - "@npmcli/promise-spawn": "^1.2.0", - "@npmcli/run-script": "^1.8.2", - "cacache": "^15.0.5", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "infer-owner": "^1.0.4", - "minipass": "^3.1.3", - "mkdirp": "^1.0.3", - "npm-package-arg": "^8.0.1", - "npm-packlist": "^2.1.4", - "npm-pick-manifest": "^6.0.0", - "npm-registry-fetch": "^11.0.0", - "promise-retry": "^2.0.1", - "read-package-json-fast": "^2.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.1.0" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "postcss-colormin": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", + "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", "dev": true, "requires": { - "callsites": "^3.0.0" + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" } }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "postcss-convert-values": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.1.tgz", + "integrity": "sha512-UjcYfl3wJJdcabGKk8lgetPvhi1Et7VDc3sYr9EyhNBeB00YD4vHgPBp+oMVoG/dDWCc6ASbmzPNV6jADTwh8Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "browserslist": "^4.20.3", + "postcss-value-parser": "^4.2.0" } }, - "parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true - }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "optional": true - }, - "parse5-html-rewriting-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-6.0.1.tgz", - "integrity": "sha512-vwLQzynJVEfUlURxgnf51yAJDQTtVpNyGD8tKi2Za7m+akukNHxCcUQMAa/mUGLhCeicFdpy7Tlvj8ZNKadprg==", + "postcss-custom-media": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", + "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", "dev": true, "requires": { - "parse5": "^6.0.1", - "parse5-sax-parser": "^6.0.1" + "postcss": "^7.0.14" }, "dependencies": { - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, - "parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "postcss-custom-properties": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", + "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", "dev": true, "requires": { - "parse5": "^6.0.1" + "postcss": "^7.0.17", + "postcss-values-parser": "^2.0.1" }, "dependencies": { - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, - "parse5-sax-parser": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", - "integrity": "sha512-kXX+5S81lgESA0LsDuGjAlBybImAChYRMT+/uKCEXFBFOeEhS52qUCydGhU3qLRD8D9DVjaUo821WK7DM4iCeg==", + "postcss-custom-selectors": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", + "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", "dev": true, "requires": { - "parse5": "^6.0.1" + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", "dev": true - } - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "piscina": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.1.0.tgz", - "integrity": "sha512-KTW4sjsCD34MHrUbx9eAAbuUSpVj407hQSgk/6Epkg0pbRBmv4a3UX7Sr8wxm9xYqQLnsN4mFOjqGDzHAdgKQg==", - "dev": true, - "requires": { - "eventemitter-asyncresource": "^1.0.0", - "hdr-histogram-js": "^2.0.1", - "hdr-histogram-percentiles-obj": "^3.0.0", - "nice-napi": "^1.0.2" + }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "postcss-dir-pseudo-class": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", + "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", "dev": true, "requires": { - "find-up": "^4.0.0" + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "picocolors": "^0.2.1", + "source-map": "^0.6.1" } }, - "locate-path": { + "postcss-selector-parser": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" } }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-discard-comments": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz", + "integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==", + "dev": true + }, + "postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "dev": true + }, + "postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "dev": true + }, + "postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "dev": true + }, + "postcss-double-position-gradients": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", + "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", + "dev": true, + "requires": { + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "picocolors": "^0.2.1", + "source-map": "^0.6.1" } }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, - "portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "postcss-env-function": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", + "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", "dev": true, "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" }, "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", "dev": true, "requires": { - "minimist": "^1.2.5" + "picocolors": "^0.2.1", + "source-map": "^0.6.1" } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "8.4.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.4.tgz", - "integrity": "sha512-joU6fBsN6EIer28Lj6GDFoC/5yOZzLCfn0zHAn/MYXI7aPt4m4hK5KC5ovEZXy+lnCjmYIbQWngvju2ddyEr8Q==", - "requires": { - "nanoid": "^3.1.30", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.1" - } - }, - "postcss-attribute-case-insensitive": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz", - "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==", + "postcss-flexbugs-fixes": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz", + "integrity": "sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ==", "dev": true, "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^6.0.2" + "postcss": "^7.0.26" }, "dependencies": { "picocolors": { @@ -8694,24 +20147,46 @@ } } }, - "postcss-calc": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.0.0.tgz", - "integrity": "sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g==", + "postcss-focus-visible": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", + "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" + "postcss": "^7.0.2" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "postcss-color-functional-notation": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", - "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", + "postcss-focus-within": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", + "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", "dev": true, "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" + "postcss": "^7.0.2" }, "dependencies": { "picocolors": { @@ -8738,15 +20213,13 @@ } } }, - "postcss-color-gray": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", - "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", + "postcss-font-variant": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz", + "integrity": "sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA==", "dev": true, "requires": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.5", - "postcss-values-parser": "^2.0.0" + "postcss": "^7.0.2" }, "dependencies": { "picocolors": { @@ -8773,14 +20246,13 @@ } } }, - "postcss-color-hex-alpha": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", - "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", + "postcss-gap-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", + "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", "dev": true, "requires": { - "postcss": "^7.0.14", - "postcss-values-parser": "^2.0.1" + "postcss": "^7.0.2" }, "dependencies": { "picocolors": { @@ -8807,13 +20279,12 @@ } } }, - "postcss-color-mod-function": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", - "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", + "postcss-image-set-function": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", + "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", "dev": true, "requires": { - "@csstools/convert-colors": "^1.4.0", "postcss": "^7.0.2", "postcss-values-parser": "^2.0.0" }, @@ -8842,14 +20313,24 @@ } } }, - "postcss-color-rebeccapurple": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", - "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", + "postcss-import": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.0.2.tgz", + "integrity": "sha512-BJ2pVK4KhUyMcqjuKs9RijV5tatNzNa73e/32aBVE/ejYPe37iH+6vAu9WvqUkB5OAYgLHzbSvzHnorybJCm9g==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-initial": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.4.tgz", + "integrity": "sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg==", "dev": true, "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" + "postcss": "^7.0.2" }, "dependencies": { "picocolors": { @@ -8876,34 +20357,15 @@ } } }, - "postcss-colormin": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.2.1.tgz", - "integrity": "sha512-VVwMrEYLcHYePUYV99Ymuoi7WhKrMGy/V9/kTS0DkCoJYmmjdOMneyhzYUxcNgteKDVbrewOkSM7Wje/MFwxzA==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-convert-values": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz", - "integrity": "sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-custom-media": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", - "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", + "postcss-lab-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", + "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", "dev": true, "requires": { - "postcss": "^7.0.14" + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" }, "dependencies": { "picocolors": { @@ -8930,14 +20392,24 @@ } } }, - "postcss-custom-properties": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", - "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", + "postcss-loader": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.1.1.tgz", + "integrity": "sha512-lBmJMvRh1D40dqpWKr9Rpygwxn8M74U9uaCSeYGNKLGInbk9mXBt1ultHf2dH9Ghk6Ue4UXlXWwGMH9QdUJ5ug==", "dev": true, "requires": { - "postcss": "^7.0.17", - "postcss-values-parser": "^2.0.1" + "cosmiconfig": "^7.0.0", + "klona": "^2.0.4", + "semver": "^7.3.5" + } + }, + "postcss-logical": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", + "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", + "dev": true, + "requires": { + "postcss": "^7.0.2" }, "dependencies": { "picocolors": { @@ -8964,22 +20436,15 @@ } } }, - "postcss-custom-selectors": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", - "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", + "postcss-media-minmax": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", + "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", "dev": true, "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" + "postcss": "^7.0.2" }, "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", - "dev": true - }, "picocolors": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", @@ -8996,17 +20461,6 @@ "source-map": "^0.6.1" } }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "dev": true, - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9015,22 +20469,112 @@ } } }, - "postcss-dir-pseudo-class": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", - "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", + "postcss-merge-longhand": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.5.tgz", + "integrity": "sha512-NOG1grw9wIO+60arKa2YYsrbgvP6tp+jqc7+ZD5/MalIw234ooH2C6KlR6FEn4yle7GqZoBxSK1mLBE9KPur6w==", "dev": true, "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.0" + } + }, + "postcss-merge-rules": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.1.tgz", + "integrity": "sha512-8wv8q2cXjEuCcgpIB1Xx1pIy8/rhMPIQqYKNzEdyx37m6gpq83mQQdCxgIkFgliyEnKvdwJf/C61vN4tQDq4Ww==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dev": true, + "requires": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-params": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz", + "integrity": "sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-selectors": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.0.tgz", + "integrity": "sha512-vYxvHkW+iULstA+ctVNx0VoRAR4THQQRkG77o0oa4/mBS0OzGvvzLIvHDv/nNEM0crzN2WIyFU5X7wZhaUK3RA==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-nesting": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", + "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "dev": true, + "requires": { + "postcss": "^7.0.2" }, "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", - "dev": true - }, "picocolors": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", @@ -9047,17 +20591,6 @@ "source-map": "^0.6.1" } }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "dev": true, - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9066,38 +20599,103 @@ } } }, - "postcss-discard-comments": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz", - "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==", + "postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", "dev": true }, - "postcss-discard-duplicates": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz", - "integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==", - "dev": true + "postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } }, - "postcss-discard-empty": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz", - "integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==", - "dev": true + "postcss-normalize-positions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.0.tgz", + "integrity": "sha512-8gmItgA4H5xiUxgN/3TVvXRoJxkAWLW6f/KKhdsH03atg0cB8ilXnrB5PpSshwVu/dD2ZsRFQcR1OEmSBDAgcQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } }, - "postcss-discard-overridden": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz", - "integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==", - "dev": true + "postcss-normalize-repeat-style": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.0.tgz", + "integrity": "sha512-IR3uBjc+7mcWGL6CtniKNQ4Rr5fTxwkaDHwMBDGGs1x9IVRkYIT/M4NelZWkAOBdV6v3Z9S46zqaKGlyzHSchw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } }, - "postcss-double-position-gradients": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", - "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", + "postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-unicode": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz", + "integrity": "sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dev": true, + "requires": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-ordered-values": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.1.tgz", + "integrity": "sha512-7lxgXF0NaoMIgyihL/2boNAEZKiW0+HkMhdKMTD93CjW8TdCy2hSdj8lsAo+uwm7EDG16Da2Jdmtqpedl0cMfw==", + "dev": true, + "requires": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-overflow-shorthand": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", + "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", "dev": true, "requires": { - "postcss": "^7.0.5", - "postcss-values-parser": "^2.0.0" + "postcss": "^7.0.2" }, "dependencies": { "picocolors": { @@ -9124,14 +20722,13 @@ } } }, - "postcss-env-function": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", - "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", + "postcss-page-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", + "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", "dev": true, "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" + "postcss": "^7.0.2" }, "dependencies": { "picocolors": { @@ -9158,13 +20755,14 @@ } } }, - "postcss-focus-visible": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", - "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", + "postcss-place": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", + "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", "dev": true, "requires": { - "postcss": "^7.0.2" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" }, "dependencies": { "picocolors": { @@ -9191,13 +20789,49 @@ } } }, - "postcss-focus-within": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", - "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", + "postcss-preset-env": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", + "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", "dev": true, "requires": { - "postcss": "^7.0.2" + "autoprefixer": "^9.6.1", + "browserslist": "^4.6.4", + "caniuse-lite": "^1.0.30000981", + "css-blank-pseudo": "^0.1.4", + "css-has-pseudo": "^0.10.0", + "css-prefers-color-scheme": "^3.1.1", + "cssdb": "^4.4.0", + "postcss": "^7.0.17", + "postcss-attribute-case-insensitive": "^4.0.1", + "postcss-color-functional-notation": "^2.0.1", + "postcss-color-gray": "^5.0.0", + "postcss-color-hex-alpha": "^5.0.3", + "postcss-color-mod-function": "^3.0.3", + "postcss-color-rebeccapurple": "^4.0.1", + "postcss-custom-media": "^7.0.8", + "postcss-custom-properties": "^8.0.11", + "postcss-custom-selectors": "^5.1.2", + "postcss-dir-pseudo-class": "^5.0.0", + "postcss-double-position-gradients": "^1.0.0", + "postcss-env-function": "^2.0.2", + "postcss-focus-visible": "^4.0.0", + "postcss-focus-within": "^3.0.0", + "postcss-font-variant": "^4.0.0", + "postcss-gap-properties": "^2.0.0", + "postcss-image-set-function": "^3.0.1", + "postcss-initial": "^3.0.0", + "postcss-lab-function": "^2.0.1", + "postcss-logical": "^3.0.0", + "postcss-media-minmax": "^4.0.0", + "postcss-nesting": "^7.0.0", + "postcss-overflow-shorthand": "^2.0.0", + "postcss-page-break": "^2.0.0", + "postcss-place": "^4.0.1", + "postcss-pseudo-class-any-link": "^6.0.0", + "postcss-replace-overflow-wrap": "^3.0.0", + "postcss-selector-matches": "^4.0.0", + "postcss-selector-not": "^4.0.0" }, "dependencies": { "picocolors": { @@ -9224,15 +20858,22 @@ } } }, - "postcss-font-variant": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz", - "integrity": "sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA==", + "postcss-pseudo-class-any-link": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", + "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", "dev": true, "requires": { - "postcss": "^7.0.2" + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, "picocolors": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", @@ -9249,6 +20890,17 @@ "source-map": "^0.6.1" } }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9257,10 +20909,29 @@ } } }, - "postcss-gap-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", - "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", + "postcss-reduce-initial": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz", + "integrity": "sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-replace-overflow-wrap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", + "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", "dev": true, "requires": { "postcss": "^7.0.2" @@ -9290,14 +20961,14 @@ } } }, - "postcss-image-set-function": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", - "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", + "postcss-selector-matches": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", + "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", "dev": true, "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" }, "dependencies": { "picocolors": { @@ -9324,23 +20995,13 @@ } } }, - "postcss-import": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.0.2.tgz", - "integrity": "sha512-BJ2pVK4KhUyMcqjuKs9RijV5tatNzNa73e/32aBVE/ejYPe37iH+6vAu9WvqUkB5OAYgLHzbSvzHnorybJCm9g==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - }, - "postcss-initial": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.4.tgz", - "integrity": "sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg==", + "postcss-selector-not": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz", + "integrity": "sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ==", "dev": true, "requires": { + "balanced-match": "^1.0.0", "postcss": "^7.0.2" }, "dependencies": { @@ -9368,659 +21029,993 @@ } } }, - "postcss-lab-function": { + "postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + } + }, + "postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "postcss-values-parser": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", - "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", + "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", "dev": true, "requires": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prettier": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz", + "integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==", + "dev": true + }, + "pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true + }, + "pretty-error": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", + "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", + "dev": true, + "requires": { + "lodash": "^4.17.20", + "renderkid": "^2.0.4" + } + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" }, "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true } } }, - "postcss-loader": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.1.1.tgz", - "integrity": "sha512-lBmJMvRh1D40dqpWKr9Rpygwxn8M74U9uaCSeYGNKLGInbk9mXBt1ultHf2dH9Ghk6Ue4UXlXWwGMH9QdUJ5ug==", + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "prismjs": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.28.0.tgz", + "integrity": "sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "promise.allsettled": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.5.tgz", + "integrity": "sha512-tVDqeZPoBC0SlzJHzWGZ2NKAguVq2oiYj7gbggbiTvH2itHohijTp7njOUA0aQ/nl+0lr/r6egmhoYu63UZ/pQ==", "dev": true, "requires": { - "cosmiconfig": "^7.0.0", - "klona": "^2.0.4", - "semver": "^7.3.5" + "array.prototype.map": "^1.0.4", + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "iterate-value": "^1.0.2" } }, - "postcss-logical": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", - "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", + "promise.prototype.finally": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/promise.prototype.finally/-/promise.prototype.finally-3.1.3.tgz", + "integrity": "sha512-EXRF3fC9/0gz4qkt/f5EP5iW4kj9oFpBICNpCNOb/52+8nlHIX07FPLbi/q4qYBQ1xZqivMzTpNQSnArVASolQ==", "dev": true, "requires": { - "postcss": "^7.0.2" - }, - "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" } }, - "postcss-media-minmax": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", - "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, "requires": { - "postcss": "^7.0.2" - }, - "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" } }, - "postcss-merge-longhand": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.0.4.tgz", - "integrity": "sha512-2lZrOVD+d81aoYkZDpWu6+3dTAAGkCKbV5DoRhnIR7KOULVrI/R7bcMjhrH9KTRy6iiHKqmtG+n/MMj1WmqHFw==", + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, "requires": { - "postcss-value-parser": "^4.1.0", - "stylehacks": "^5.0.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" } }, - "postcss-merge-rules": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.0.3.tgz", - "integrity": "sha512-cEKTMEbWazVa5NXd8deLdCnXl+6cYG7m2am+1HzqH0EnTdy8fRysatkaXb2dEnR+fdaDxTvuZ5zoBdv6efF6hg==", + "property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", "dev": true, "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^2.0.1", - "postcss-selector-parser": "^6.0.5" + "xtend": "^4.0.0" } }, - "postcss-minify-font-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz", - "integrity": "sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA==", + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, "requires": { - "postcss-value-parser": "^4.1.0" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" } }, - "postcss-minify-gradients": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz", - "integrity": "sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q==", + "proxy-middleware": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", + "integrity": "sha1-o/3xvvtzD5UZZYcqwvYHTGFHelY=", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "dev": true, "requires": { - "colord": "^2.9.1", - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } } }, - "postcss-minify-params": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.0.2.tgz", - "integrity": "sha512-qJAPuBzxO1yhLad7h2Dzk/F7n1vPyfHfCCh5grjGfjhi1ttCnq4ZXGIW77GSrEbh9Hus9Lc/e/+tB4vh3/GpDg==", + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { - "alphanum-sort": "^1.0.2", - "browserslist": "^4.16.6", - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "postcss-minify-selectors": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz", - "integrity": "sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og==", + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { - "alphanum-sort": "^1.0.2", - "postcss-selector-parser": "^6.0.5" + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } } }, - "postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, - "postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", "dev": true, "requires": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" + "side-channel": "^1.0.4" } }, - "postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "quote-stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", + "integrity": "sha1-hJY/jJwmuULhU/7rU6rnRlK34LI=", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.4" + "buffer-equal": "0.0.1", + "minimist": "^1.1.3", + "through2": "^2.0.0" } }, - "postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "ramda": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", + "integrity": "sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "requires": { - "icss-utils": "^5.0.0" + "safe-buffer": "^5.1.0" } }, - "postcss-nesting": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", - "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dev": true, "requires": { - "postcss": "^7.0.2" + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" }, "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true } } }, - "postcss-normalize-charset": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz", - "integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==", - "dev": true + "raw-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + } }, - "postcss-normalize-display-values": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz", - "integrity": "sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ==", + "react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", "dev": true, "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" } }, - "postcss-normalize-positions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz", - "integrity": "sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg==", + "react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", "dev": true, "requires": { - "postcss-value-parser": "^4.1.0" + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" } }, - "postcss-normalize-repeat-style": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz", - "integrity": "sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w==", + "react-inspector": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-5.1.1.tgz", + "integrity": "sha512-GURDaYzoLbW8pMGXwYPDBIv6nqei4kK7LPRZ9q9HCZF54wqXz/dnylBp/kfE9XmekBhHvLDdcYeyIwSrvtOiWg==", "dev": true, "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "@babel/runtime": "^7.0.0", + "is-dom": "^1.0.0", + "prop-types": "^15.0.0" } }, - "postcss-normalize-string": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz", - "integrity": "sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA==", + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "react-merge-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz", + "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==", + "dev": true + }, + "react-syntax-highlighter": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", + "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", "dev": true, "requires": { - "postcss-value-parser": "^4.1.0" + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" } }, - "postcss-normalize-timing-functions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz", - "integrity": "sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q==", + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", "dev": true, "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, - "postcss-normalize-unicode": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz", - "integrity": "sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA==", + "read-package-json-fast": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", + "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", "dev": true, "requires": { - "browserslist": "^4.16.0", - "postcss-value-parser": "^4.1.0" + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" } }, - "postcss-normalize-url": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.0.3.tgz", - "integrity": "sha512-qWiUMbvkRx3kc1Dp5opzUwc7MBWZcSDK2yofCmdvFBCpx+zFPkxBC1FASQ59Pt+flYfj/nTZSkmF56+XG5elSg==", + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "is-absolute-url": "^3.0.3", - "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.1.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } } }, - "postcss-normalize-whitespace": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz", - "integrity": "sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA==", + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "requires": { - "postcss-value-parser": "^4.1.0" + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } } }, - "postcss-ordered-values": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz", - "integrity": "sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ==", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "picomatch": "^2.2.1" } }, - "postcss-overflow-shorthand": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", - "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, + "optional": true, "requires": { - "postcss": "^7.0.2" + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" }, "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, + "optional": true, "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" + "repeating": "^2.0.0" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, - "postcss-page-break": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", - "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", "dev": true, "requires": { - "postcss": "^7.0.2" + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" }, "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", "dev": true } } }, - "postcss-place": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", - "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", "dev": true, "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - }, - "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "regenerate": "^1.4.2" } }, - "postcss-preset-env": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", - "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", "dev": true, "requires": { - "autoprefixer": "^9.6.1", - "browserslist": "^4.6.4", - "caniuse-lite": "^1.0.30000981", - "css-blank-pseudo": "^0.1.4", - "css-has-pseudo": "^0.10.0", - "css-prefers-color-scheme": "^3.1.1", - "cssdb": "^4.4.0", - "postcss": "^7.0.17", - "postcss-attribute-case-insensitive": "^4.0.1", - "postcss-color-functional-notation": "^2.0.1", - "postcss-color-gray": "^5.0.0", - "postcss-color-hex-alpha": "^5.0.3", - "postcss-color-mod-function": "^3.0.3", - "postcss-color-rebeccapurple": "^4.0.1", - "postcss-custom-media": "^7.0.8", - "postcss-custom-properties": "^8.0.11", - "postcss-custom-selectors": "^5.1.2", - "postcss-dir-pseudo-class": "^5.0.0", - "postcss-double-position-gradients": "^1.0.0", - "postcss-env-function": "^2.0.2", - "postcss-focus-visible": "^4.0.0", - "postcss-focus-within": "^3.0.0", - "postcss-font-variant": "^4.0.0", - "postcss-gap-properties": "^2.0.0", - "postcss-image-set-function": "^3.0.1", - "postcss-initial": "^3.0.0", - "postcss-lab-function": "^2.0.1", - "postcss-logical": "^3.0.0", - "postcss-media-minmax": "^4.0.0", - "postcss-nesting": "^7.0.0", - "postcss-overflow-shorthand": "^2.0.0", - "postcss-page-break": "^2.0.0", - "postcss-place": "^4.0.1", - "postcss-pseudo-class-any-link": "^6.0.0", - "postcss-replace-overflow-wrap": "^3.0.0", - "postcss-selector-matches": "^4.0.0", - "postcss-selector-not": "^4.0.0" + "@babel/runtime": "^7.8.4" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "regexpu-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", + "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", + "dev": true, + "requires": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + } + }, + "regjsgen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "regjsparser": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" }, "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true } } }, - "postcss-pseudo-class-any-link": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", - "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remark-external-links": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/remark-external-links/-/remark-external-links-8.0.0.tgz", + "integrity": "sha512-5vPSX0kHoSsqtdftSHhIYofVINC8qmp0nctkeU9YoJwV3YfiBRiI6cbFRJ0oI/1F9xS+bopXG0m2KS8VFscuKA==", "dev": true, "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" + "extend": "^3.0.0", + "is-absolute-url": "^3.0.0", + "mdast-util-definitions": "^4.0.0", + "space-separated-tokens": "^1.0.0", + "unist-util-visit": "^2.0.0" + } + }, + "remark-footnotes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/remark-footnotes/-/remark-footnotes-2.0.0.tgz", + "integrity": "sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ==", + "dev": true + }, + "remark-mdx": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.22.tgz", + "integrity": "sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==", + "dev": true, + "requires": { + "@babel/core": "7.12.9", + "@babel/helper-plugin-utils": "7.10.4", + "@babel/plugin-proposal-object-rest-spread": "7.12.1", + "@babel/plugin-syntax-jsx": "7.12.1", + "@mdx-js/util": "1.6.22", + "is-alphabetical": "1.0.4", + "remark-parse": "8.0.3", + "unified": "9.2.0" }, "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", - "dev": true + "@babel/core": { + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", + "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.7", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.9", + "@babel/types": "^7.12.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } }, - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", "dev": true }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", "dev": true, "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.12.1" } }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "@babel/plugin-syntax-jsx": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", + "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", "dev": true, "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true } } }, - "postcss-reduce-initial": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.0.2.tgz", - "integrity": "sha512-v/kbAAQ+S1V5v9TJvbGkV98V2ERPdU6XvMcKMjqAlYiJ2NtsHGlKYLPjWWcXlaTKNxooId7BGxeraK8qXvzKtw==", + "remark-parse": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz", + "integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==", "dev": true, "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0" + "ccount": "^1.0.0", + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^2.0.0", + "vfile-location": "^3.0.0", + "xtend": "^4.0.1" } }, - "postcss-reduce-transforms": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz", - "integrity": "sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA==", + "remark-slug": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/remark-slug/-/remark-slug-6.1.0.tgz", + "integrity": "sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ==", "dev": true, "requires": { - "cssnano-utils": "^2.0.1", - "postcss-value-parser": "^4.1.0" + "github-slugger": "^1.0.0", + "mdast-util-to-string": "^1.0.0", + "unist-util-visit": "^2.0.0" } }, - "postcss-replace-overflow-wrap": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", - "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", + "remark-squeeze-paragraphs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz", + "integrity": "sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw==", "dev": true, "requires": { - "postcss": "^7.0.2" + "mdast-squeeze-paragraphs": "^4.0.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "renderkid": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz", + "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==", + "dev": true, + "requires": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^3.0.1" }, "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", "dev": true, "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } } } }, - "postcss-selector-matches": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", - "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", + "repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, + "optional": true, "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" + "is-finite": "^1.0.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" }, "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true } } }, - "postcss-selector-not": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz", - "integrity": "sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ==", + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "resolve-url-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", + "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^7.0.35", + "source-map": "0.6.1" }, "dependencies": { "picocolors": { @@ -10047,682 +22042,693 @@ } } }, - "postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-svgo": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.0.3.tgz", - "integrity": "sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.1.0", - "svgo": "^2.7.0" - } - }, - "postcss-unique-selectors": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.0.2.tgz", - "integrity": "sha512-w3zBVlrtZm7loQWRPVC0yjUwwpty7OM6DnEHkxcSQXO1bMS3RJ+JUS5LFMSDZHJcvGsRwhZinCWVqn8Kej4EDA==", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.2", - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "postcss-values-parser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", - "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" } }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, - "pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", "dev": true }, - "promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { - "err-code": "^2.0.2", - "retry": "^0.12.0" + "glob": "^7.1.3" } }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "dev": true, "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "hash-base": "^3.0.0", + "inherits": "^2.0.1" } }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", "dev": true }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "queue-microtask": "^1.2.2" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "dev": true, "requires": { - "safe-buffer": "^5.1.0" + "aproba": "^1.1.1" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + } } }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dev": true, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "tslib": "^1.9.0" }, "dependencies": { - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" } } }, - "raw-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", - "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" + "ret": "~0.1.10" } }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", "dev": true, "requires": { - "pify": "^2.3.0" + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" }, "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } } } }, - "read-package-json-fast": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", - "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", + "sass": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.36.0.tgz", + "integrity": "sha512-fQzEjipfOv5kh930nu3Imzq3ie/sGDc/4KtQMJlt7RRdrkQSfe37Bwi/Rf/gfuYHsIuE1fIlDMvpyMcEwjnPvg==", "dev": true, "requires": { - "json-parse-even-better-errors": "^2.3.0", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "chokidar": ">=3.0.0 <4.0.0" } }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "sass-loader": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.1.0.tgz", + "integrity": "sha512-FVJZ9kxVRYNZTIe2xhw93n3xJNYZADr+q69/s98l9nTCrWASo+DR2Ot0s5xTKQDDEosUkatsGeHxcH4QBp5bSg==", "dev": true, "requires": { - "picomatch": "^2.2.1" + "klona": "^2.0.4", + "neo-async": "^2.6.2" } }, - "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, - "regenerate-unicode-properties": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", - "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", + "scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", "dev": true, "requires": { - "regenerate": "^1.4.2" + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" } }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true - }, - "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dev": true, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", "requires": { - "@babel/runtime": "^7.8.4" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "scope-analyzer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/scope-analyzer/-/scope-analyzer-2.1.2.tgz", + "integrity": "sha512-5cfCmsTYV/wPaRIItNxatw02ua/MThdIUNnUOCYp+3LSEJvnG804ANw2VLaavNILIfWXF1D1G2KNANkBBvInwQ==", "dev": true, "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "array-from": "^2.1.1", + "dash-ast": "^2.0.1", + "es6-map": "^0.1.5", + "es6-set": "^0.1.5", + "es6-symbol": "^3.1.1", + "estree-is-function": "^1.0.0", + "get-assigned-identifiers": "^1.1.0" } }, - "regex-parser": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", - "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", "dev": true }, - "regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "selfsigned": { + "version": "1.10.14", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.14.tgz", + "integrity": "sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "node-forge": "^0.10.0" } }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "regexpu-core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", - "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" + "lru-cache": "^6.0.0" } }, - "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", - "dev": true - }, - "regjsparser": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", - "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dev": true, "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, "dependencies": { - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true } } }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "randombytes": "^2.1.0" } }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "serve-favicon": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.5.0.tgz", + "integrity": "sha1-k10kDN/g9YBTB/3+ln2IlCosvPA=", "dev": true, "requires": { - "resolve-from": "^3.0.0" + "etag": "~1.8.1", + "fresh": "0.5.2", + "ms": "2.1.1", + "parseurl": "~1.3.2", + "safe-buffer": "5.1.1" }, "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true } } }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "resolve-url-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", - "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^7.0.35", - "source-map": "0.6.1" + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" }, "dependencies": { - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true } } }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dev": true, "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "requires": { - "glob": "^7.1.3" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, - "run-parallel": { + "setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { - "queue-microtask": "^1.2.2" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, "requires": { - "tslib": "^1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } + "kind-of": "^6.0.2" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", + "dev": true }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "ret": "~0.1.10" + "shebang-regex": "^1.0.0" } }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, - "sass": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.36.0.tgz", - "integrity": "sha512-fQzEjipfOv5kh930nu3Imzq3ie/sGDc/4KtQMJlt7RRdrkQSfe37Bwi/Rf/gfuYHsIuE1fIlDMvpyMcEwjnPvg==", - "dev": true, + "showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", "requires": { - "chokidar": ">=3.0.0 <4.0.0" + "commander": "^9.0.0" } }, - "sass-loader": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.1.0.tgz", - "integrity": "sha512-FVJZ9kxVRYNZTIe2xhw93n3xJNYZADr+q69/s98l9nTCrWASo+DR2Ot0s5xTKQDDEosUkatsGeHxcH4QBp5bSg==", + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dev": true, "requires": { - "klona": "^2.0.4", - "neo-async": "^2.6.2" + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" } }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "selfsigned": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", - "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { - "node-forge": "^0.10.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } } }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" }, "dependencies": { "debug": { @@ -10732,259 +22738,428 @@ "dev": true, "requires": { "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true } } }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "randombytes": "^2.1.0" + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } } }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "socket.io": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", + "integrity": "sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ==", "dev": true, "requires": { "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.0", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.0.4" + } + }, + "socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", + "dev": true + }, + "socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "dev": true, + "requires": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + } + }, + "sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "requires": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "sockjs-client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.0.tgz", + "integrity": "sha512-qVHJlyfdHFht3eBFZdKEXKTlb7I4IV41xnVNo8yUKA1UHcPJwgW2SvTq9LhnjjCywSkSK7c/e4nghU0GOoMCRQ==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "eventsource": "^1.1.0", + "faye-websocket": "^0.11.4", + "inherits": "^2.0.4", + "url-parse": "^1.5.10" }, "dependencies": { "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "ms": "^2.1.1" } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true } } }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "socks": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", + "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", "dev": true, "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" + "ip": "^1.1.5", + "smart-buffer": "^4.2.0" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "socks-proxy-agent": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.0.tgz", + "integrity": "sha512-wWqJhjb32Q6GsrUqzuFkukxb/zzide5quXYcMVpIjxalDBBYy2nqKCFQ/9+Ie4dvOYSQdOk3hUlZSdzZOd3zMQ==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", "dev": true }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, + "source-map-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.0.tgz", + "integrity": "sha512-GKGWqWvYr04M7tn8dryIWvb0s8YM41z82iQv01yBtIylgxax0CwvSy6gc2Y02iuXwEfGWRlMicH0nvms9UZphw==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "abab": "^2.0.5", + "iconv-lite": "^0.6.2", + "source-map-js": "^0.6.2" }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" } + }, + "source-map-js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "dev": true } } }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", "dev": true, "requires": { - "kind-of": "^6.0.2" + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" } }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, - "showdown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.0.0.tgz", - "integrity": "sha512-Gz0wkh/EBFbEH+Nb85nyRSrcRvPecgt8c6fk/ICaOQ2dVsWCvZU5jfViPtBIyLXVYooICO0WTl7vLsoPaIH09w==", + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, "requires": { - "yargs": "^17.2.1" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "signal-exit": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", "dev": true }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "color-name": "~1.1.4" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true } } }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "state-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", + "dev": true + }, + "static-eval": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.0.tgz", + "integrity": "sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw==", + "dev": true, + "requires": { + "escodegen": "^1.11.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "object-copy": "^0.1.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -10993,257 +23168,521 @@ "requires": { "is-descriptor": "^0.1.0" } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "static-module": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/static-module/-/static-module-3.0.4.tgz", + "integrity": "sha512-gb0v0rrgpBkifXCa3yZXxqVmXDVE+ETXj6YlC/jt5VzOnGXR2C15+++eXuMDUYsePnbhf+lwW0pE1UXyOLtGCw==", "dev": true, "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "acorn-node": "^1.3.0", + "concat-stream": "~1.6.0", + "convert-source-map": "^1.5.1", + "duplexer2": "~0.1.4", + "escodegen": "^1.11.1", + "has": "^1.0.1", + "magic-string": "0.25.1", + "merge-source-map": "1.0.4", + "object-inspect": "^1.6.0", + "readable-stream": "~2.3.3", + "scope-analyzer": "^2.0.1", + "shallow-copy": "~0.0.1", + "static-eval": "^2.0.5", + "through2": "~2.0.3" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "magic-string": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", + "integrity": "sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "sourcemap-codec": "^1.4.1" } }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "merge-source-map": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", + "integrity": "sha1-pd5GU42uhNQRTMXqArR3KmNGcB8=", "dev": true, "requires": { - "kind-of": "^6.0.0" + "source-map": "^0.5.6" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "store2": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/store2/-/store2-2.13.2.tgz", + "integrity": "sha512-CMtO2Uneg3SAz/d6fZ/6qbqqQHi2ynq6/KzMD/26gTkiEShCcpqFfTHgOxsE0egAq6SX3FmN4CeSqn8BzXQkJg==", + "dev": true + }, + "storybook-dark-mode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/storybook-dark-mode/-/storybook-dark-mode-1.1.0.tgz", + "integrity": "sha512-F+hG02zYGBzxGTUonA1XDV/CtMYm3OjF38Tu1CIUN+w+8hwUrwLcOtgtLLw6VjSrZdJ/ECK+tjXdKTV4oZqAXw==", "dev": true, "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "fast-deep-equal": "^3.0.0", + "memoizerific": "^1.11.3" } }, - "socket.io": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", - "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "dev": true, "requires": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.1.0", - "socket.io-adapter": "~2.3.3", - "socket.io-parser": "~4.0.4" + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" } }, - "socket.io-adapter": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", - "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", + "stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "streamroller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.1.tgz", + "integrity": "sha512-iPhtd9unZ6zKdWgMeYGfSBuqCngyJy1B/GPi/lTpwGpa3bajuX30GjUVd0/Tn/Xhg0mr4DOSENozz9Y06qyonQ==", + "dev": true, + "requires": { + "date-format": "^4.0.10", + "debug": "^4.3.4", + "fs-extra": "^10.1.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + } + }, + "string.prototype.padend": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", + "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "string.prototype.padstart": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padstart/-/string.prototype.padstart-3.1.3.tgz", + "integrity": "sha512-NZydyOMtYxpTjGqp0VN5PYUF/tsU15yDMZnUdj16qRUIUiMJkHHSDElYyQFrMu+/WloTpA7MQSiADhBicDfaoA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "optional": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "optional": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "style-loader": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.2.1.tgz", + "integrity": "sha512-1k9ZosJCRFaRbY6hH49JFlRB0fVSbmnyq1iTPjNxUmGVjBNEmwrrHPenhlp+Lgo51BojHSf6pl2FcqYaN3PfVg==", "dev": true }, - "socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "style-to-object": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", + "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==", "dev": true, "requires": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", - "debug": "~4.3.1" + "inline-style-parser": "0.1.1" } }, - "sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", + "stylehacks": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", + "integrity": "sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==", "dev": true, "requires": { - "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", - "websocket-driver": "^0.7.4" + "browserslist": "^4.16.6", + "postcss-selector-parser": "^6.0.4" } }, - "sockjs-client": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz", - "integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==", + "stylus": { + "version": "0.54.8", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz", + "integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==", "dev": true, "requires": { - "debug": "^3.2.6", - "eventsource": "^1.0.7", - "faye-websocket": "^0.11.3", - "inherits": "^2.0.4", - "json3": "^3.3.3", - "url-parse": "^1.5.3" + "css-parse": "~2.0.0", + "debug": "~3.1.0", + "glob": "^7.1.6", + "mkdirp": "~1.0.4", + "safer-buffer": "^2.1.2", + "sax": "~1.2.4", + "semver": "^6.3.0", + "source-map": "^0.7.3" }, "dependencies": { "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, - "socks": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", - "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", + "stylus-loader": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-6.1.0.tgz", + "integrity": "sha512-qKO34QCsOtSJrXxQQmXsPeaVHh6hMumBAFIoJTcsSr2VzrA6o/CW9HCGR8spCjzJhN8oKQHdj/Ytx0wwXyElkw==", "dev": true, "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.1.0" + "fast-glob": "^3.2.5", + "klona": "^2.0.4", + "normalize-path": "^3.0.0" } }, - "socks-proxy-agent": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", - "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "agent-base": "^6.0.2", - "debug": "^4.3.1", - "socks": "^2.6.1" + "has-flag": "^3.0.0" } }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + } + } + }, + "symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", "dev": true }, - "source-map-js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz", - "integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==" + "symbol.prototype.description": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/symbol.prototype.description/-/symbol.prototype.description-1.0.5.tgz", + "integrity": "sha512-x738iXRYsrAt9WBhRCVG5BtIC3B7CUkFwbHW2zOvGtwM33s7JjrCDyq8V0zgMYVb5ymsL8+qkzzpANH63CPQaQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-symbol-description": "^1.0.0", + "has-symbols": "^1.0.2", + "object.getownpropertydescriptors": "^2.1.2" + } }, - "source-map-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.0.tgz", - "integrity": "sha512-GKGWqWvYr04M7tn8dryIWvb0s8YM41z82iQv01yBtIylgxax0CwvSy6gc2Y02iuXwEfGWRlMicH0nvms9UZphw==", + "synchronous-promise": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.15.tgz", + "integrity": "sha512-k8uzYIkIVwmT+TcglpdN50pS2y1BDcUnBPK9iJeGu0Pl1lOI8pD6wtzgw91Pjpe+RxtTncw32tLxs/R0yNL2Mg==", + "dev": true + }, + "table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", "dev": true, "requires": { - "abab": "^2.0.5", - "iconv-lite": "^0.6.2", - "source-map-js": "^0.6.2" + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" } }, - "source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true } } }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" } }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "telejson": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/telejson/-/telejson-6.0.8.tgz", + "integrity": "sha512-nerNXi+j8NK1QEfBHtZUN/aLdDcyupA//9kAboYLrtzZlPLpUfqbVGWb9zz91f/mIjRbAYhbgtnJHY8I1b5MBg==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "@types/is-function": "^1.0.0", + "global": "^4.4.0", + "is-function": "^1.0.2", + "is-regex": "^1.1.2", + "is-symbol": "^1.0.3", + "isobject": "^4.0.0", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3" + }, + "dependencies": { + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true + } + } + }, + "terser": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.1.tgz", + "integrity": "sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz", + "integrity": "sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA==", + "dev": true, + "requires": { + "jest-worker": "^27.0.2", + "p-limit": "^3.1.0", + "schema-utils": "^3.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1", + "terser": "^5.7.0" }, "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11252,863 +23691,1207 @@ } } }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", "dev": true }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "dev": true }, - "spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" + "os-tmpdir": "~1.0.2" } }, - "spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" + "kind-of": "^3.0.2" }, "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "is-buffer": "^1.1.5" } } } }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { - "extend-shallow": "^3.0.0" + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" + "is-number": "^7.0.0" } }, - "ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true, - "requires": { - "minipass": "^3.1.1" - } + "optional": true }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "trim-trailing-lines": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", + "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==", "dev": true }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "dev": true + }, + "ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true + }, + "ts-loader": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.4.0.tgz", + "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==", "dev": true, "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "chalk": "^4.1.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^2.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "enhanced-resolve": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true } } }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "ts-morph": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-13.0.3.tgz", + "integrity": "sha512-pSOfUMx8Ld/WUreoSzvMFQG5i9uEiWIsBYjpU9+TTASOeUa89j5HykomeqVULm1oqWtBdleI3KEFRLrlA3zGIw==", + "dev": true, + "requires": { + "@ts-morph/common": "~0.12.3", + "code-block-writer": "^11.0.0" + } + }, + "ts-pnp": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", + "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==", "dev": true }, - "streamroller": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz", - "integrity": "sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA==", + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", "dev": true, "requires": { - "date-format": "^4.0.3", - "debug": "^4.1.1", - "fs-extra": "^10.0.0" + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } } }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "tsconfig-paths-webpack-plugin": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz", + "integrity": "sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==", + "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^3.9.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { - "ansi-regex": "^5.0.1" + "has-flag": "^4.0.0" } } } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, - "style-loader": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.2.1.tgz", - "integrity": "sha512-1k9ZosJCRFaRbY6hH49JFlRB0fVSbmnyq1iTPjNxUmGVjBNEmwrrHPenhlp+Lgo51BojHSf6pl2FcqYaN3PfVg==", + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, - "stylehacks": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.0.1.tgz", - "integrity": "sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA==", + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "browserslist": "^4.16.0", - "postcss-selector-parser": "^6.0.4" + "prelude-ls": "~1.1.2" } }, - "stylus": { - "version": "0.54.8", - "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz", - "integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==", + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, "requires": { - "css-parse": "~2.0.0", - "debug": "~3.1.0", - "glob": "^7.1.6", - "mkdirp": "~1.0.4", - "safer-buffer": "^2.1.2", - "sax": "~1.2.4", - "semver": "^6.3.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "media-typer": "0.3.0", + "mime-types": "~2.1.24" } }, - "stylus-loader": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-6.1.0.tgz", - "integrity": "sha512-qKO34QCsOtSJrXxQQmXsPeaVHh6hMumBAFIoJTcsSr2VzrA6o/CW9HCGR8spCjzJhN8oKQHdj/Ytx0wwXyElkw==", + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, "requires": { - "fast-glob": "^3.2.5", - "klona": "^2.0.4", - "normalize-path": "^3.0.0" + "is-typedarray": "^1.0.0" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "typescript": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "dev": true + }, + "ua-parser-js": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", + "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "dev": true + }, + "uglify-js": { + "version": "3.15.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.5.tgz", + "integrity": "sha512-hNM5q5GbBRB5xB+PMqVRcgYe4c8jbyZ1pzZhS6jbq54/4F2gFK869ZheiE5A8/t+W5jtTNpWef/5Q9zk639FNQ==", + "dev": true, + "optional": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" } }, - "svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", + "dev": true + }, + "unherit": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", "dev": true, "requires": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" + "inherits": "^2.0.0", + "xtend": "^4.0.0" } }, - "symbol-observable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", - "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", "dev": true }, - "table": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.3.tgz", - "integrity": "sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw==", + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", - "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" } }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", "dev": true }, - "tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "unicode-properties": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.3.1.tgz", + "integrity": "sha512-nIV3Tf3LcUEZttY/2g4ZJtGXhWwSkuLL+rCu0DIAMbjyVPj+8j5gNVz4T/sVbnQybIsd5SFGkPKg/756OY6jlA==", "dev": true, "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" } }, - "terser": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.1.tgz", - "integrity": "sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg==", + "unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true + }, + "unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", "dev": true, "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" }, "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", "dev": true } } }, - "terser-webpack-plugin": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz", - "integrity": "sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA==", + "unified": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", + "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", "dev": true, "requires": { - "jest-worker": "^27.0.2", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.0" + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" }, "dependencies": { - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", "dev": true } } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", "dev": true }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unist-builder": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", + "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==", "dev": true }, - "thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "unist-util-generated": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz", + "integrity": "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==", "dev": true }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", "dev": true }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "unist-util-position": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", + "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==", + "dev": true + }, + "unist-util-remove": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-2.1.0.tgz", + "integrity": "sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "unist-util-is": "^4.0.0" } }, - "to-fast-properties": { + "unist-util-remove-position": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", + "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", + "dev": true, + "requires": { + "unist-util-visit": "^2.0.0" + } + }, + "unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "requires": { + "@types/unist": "^2.0.2" + } + }, + "unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + } + }, + "unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + } + }, + "universalify": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "has-value": "^0.3.1", + "isobject": "^3.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true } } }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "untildify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz", + "integrity": "sha1-F+soB5h/dpUunASF/DEdBqgmouA=", "dev": true, + "optional": true, "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "os-homedir": "^1.0.0" } }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "requires": { - "is-number": "^7.0.0" + "punycode": "^2.1.0" } }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "dev": true }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", "dev": true, "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } } }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true + "url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + } }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "dev": true, "requires": { - "tslib": "^1.8.1" + "inherits": "2.0.3" }, "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true } } }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", "dev": true, "requires": { - "prelude-ls": "^1.2.1" + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" } }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", "dev": true }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true }, - "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, - "ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "uuid-browser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid-browser/-/uuid-browser-3.1.0.tgz", + "integrity": "sha1-DwWkCu90+eWVHiDvv0SxGHHlZBA=", "dev": true }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "dev": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", "dev": true, "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" + "builtins": "^1.0.3" } }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "dev": true }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "vfile": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", "dev": true, "requires": { - "unique-slug": "^2.0.0" + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + } } }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "vfile-location": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.2.0.tgz", + "integrity": "sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==", + "dev": true + }, + "vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", "dev": true, "requires": { - "imurmurhash": "^0.1.4" + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" } }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" + "makeerror": "1.0.12" + } + }, + "watchpack": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", + "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "watchpack-chokidar2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "dev": true, + "optional": true, + "requires": { + "chokidar": "^2.1.8" }, "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "optional": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, + "optional": true, "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, + "optional": true, "requires": { - "isarray": "1.0.0" + "is-extglob": "^2.1.0" } } } }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "dev": true, - "requires": { - "builtins": "^1.0.3" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "dependencies": { - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } } } }, - "void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "dev": true - }, - "watchpack": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.0.tgz", - "integrity": "sha512-MnN0Q1OsvB/GGHETrFeZPQaOelWh/7O+EiFlj8sM9GPjtQkis7k01aAxrg/18kTfoIVcLL+haEVFlXDaSRwKRw==", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, "wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", @@ -12127,6 +24910,24 @@ "defaults": "^1.0.3" } }, + "web-namespaces": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz", + "integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==", + "dev": true + }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, "webpack": { "version": "5.50.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.50.0.tgz", @@ -12160,9 +24961,9 @@ }, "dependencies": { "webpack-sources": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.2.tgz", - "integrity": "sha512-cp5qdmHnu5T8wRg2G3vZZHoJPN14aqQ89SyQ11NpGH5zEMDCclt49rzo+MaRazk7/UeILhAI+/sEtcM+7Fr0nw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true } } @@ -12182,12 +24983,12 @@ } }, "webpack-dev-server": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz", - "integrity": "sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ==", + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.3.tgz", + "integrity": "sha512-3x31rjbEQWKMNzacUZRE6wXvUFuGpH7vr0lIEbYpMAG9BOxi0928QU1BBswOAP3kg3H1O4hiS+sq4YyAn6ANnA==", "dev": true, "requires": { - "ansi-html": "0.0.7", + "ansi-html-community": "0.0.8", "bonjour": "^3.5.0", "chokidar": "^2.1.8", "compression": "^1.7.4", @@ -12225,7 +25026,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true }, "anymatch": { @@ -12412,12 +25213,12 @@ "dev": true }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } }, "readdirp": { @@ -12436,46 +25237,18 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -12516,34 +25289,32 @@ "range-parser": "^1.2.1", "webpack-log": "^2.0.0" } - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + } + } + }, + "webpack-filter-warnings-plugin": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/webpack-filter-warnings-plugin/-/webpack-filter-warnings-plugin-1.2.1.tgz", + "integrity": "sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg==", + "dev": true + }, + "webpack-hot-middleware": { + "version": "2.25.1", + "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.25.1.tgz", + "integrity": "sha512-Koh0KyU/RPYwel/khxbsDz9ibDivmUbrRuKSSQvW42KSDdO4w23WI3SkHpSUKHE76LrFnnM/L7JCrpBwu8AXYw==", + "dev": true, + "requires": { + "ansi-html-community": "0.0.8", + "html-entities": "^2.1.0", + "querystring": "^0.2.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true } } }, @@ -12562,6 +25333,12 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true } } }, @@ -12602,6 +25379,26 @@ "webpack-sources": "^1.3.0" } }, + "webpack-virtual-modules": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.2.2.tgz", + "integrity": "sha512-kDUmfm3BZrei0y+1NTHJInejzxfhtU8eDj2M7OKb2IWrPFAeO1SOH2KuQ68MSZu9IGEHcxbkKKR1v18FrUSOmA==", + "dev": true, + "requires": { + "debug": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -12619,6 +25416,16 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -12628,6 +25435,19 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -12643,18 +25463,144 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + } + }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, + "windows-release": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", + "integrity": "sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==", + "dev": true, + "requires": { + "execa": "^4.0.2" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "worker-rpc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", + "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", + "dev": true, + "requires": { + "microevent.ts": "~0.1.1" + } + }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -12666,6 +25612,24 @@ "strip-ansi": "^5.0.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -12676,6 +25640,15 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } } } }, @@ -12685,6 +25658,18 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, "ws": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", @@ -12694,6 +25679,30 @@ "async-limiter": "~1.0.0" } }, + "x-default-browser": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/x-default-browser/-/x-default-browser-0.4.0.tgz", + "integrity": "sha1-cM8NqF2nwKtcsPFaiX8jIqa91IE=", + "dev": true, + "requires": { + "default-browser-id": "^1.0.4" + } + }, + "xmldoc": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.1.2.tgz", + "integrity": "sha512-ruPC/fyPNck2BD1dpz0AZZyrEwMOrWTO5lDdIXS91rs3wtm4j+T8Rp2o+zoOYkkAxJTZRPOSnOGei1egoRmKMQ==", + "dev": true, + "requires": { + "sax": "^1.2.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, "y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", @@ -12713,84 +25722,106 @@ "dev": true }, "yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" }, "dependencies": { "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, "requires": { - "color-convert": "^2.0.1" + "locate-path": "^3.0.0" } }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, "requires": { - "color-name": "~1.1.4" + "p-limit": "^2.0.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, "requires": { - "ansi-regex": "^5.0.1" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-regex": "^4.1.0" } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" } } }, "yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==" + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } }, "yocto-queue": { "version": "0.1.0", @@ -12799,12 +25830,18 @@ "dev": true }, "zone.js": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.4.tgz", - "integrity": "sha512-DDh2Ab+A/B+9mJyajPjHFPWfYU1H+pdun4wnnk0OcQTNjem1XQSZ2CDW+rfZEUDjv5M19SBqAkjZi0x5wuB5Qw==", + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.5.tgz", + "integrity": "sha512-D1/7VxEuQ7xk6z/kAROe4SUbd9CzxY4zOwVGnGHerd/SgLIVU5f4esDzQUsOCeArn933BZfWMKydH7l7dPEp0g==", "requires": { - "tslib": "^2.0.0" + "tslib": "^2.3.0" } + }, + "zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "dev": true } } } diff --git a/package.json b/package.json index bbeff84ba0061cf5596dc00a1af1b78d4c9c9b44..8045a92ddfdc2c4b3ee43a83e590b11bf7cdf35a 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,8 @@ { "name": "interactive-viewer", - "version": "2.6.10", - "description": "Siibra Explorer. Integrating KG query, dataset previews & more. Based on humanbrainproject/nehuba & google/neuroglancer. Built with angular", + "version": "2.7.0", + "description": "siibra-explorer - explore brain atlases. Based on humanbrainproject/nehuba & google/neuroglancer. Built with angular", "scripts": { - "build-aot": "ng build && node ./third_party/matomo/processMatomo.js", - "dev-server-aot": "ng serve", - "e2e": "echo NYI && exit 1", - "wd": "webdriver-manager", "lint": "eslint src --ext .ts", "eslint": "eslint", "ng": "ng", @@ -14,7 +10,12 @@ "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test", - "test-ci": "ng test --progress false --watch false --browsers=ChromeHeadless" + "test-ci": "ng test --progress false --watch false --browsers=ChromeHeadless", + "sapi-schema": "npx openapi-typescript@5.1.1 http://localhost:5000/v1_0/openapi.json --output ./src/atlasComponents/sapi/schema.ts && eslint ./src/atlasComponents/sapi/schema.ts --no-ignore --fix", + "api-schema": "node src/plugin/generateTypes.js", + "docs:json": "compodoc -p ./tsconfig.json -e json -d .", + "storybook": "npm run docs:json && start-storybook -p 6006", + "build-storybook": "npm run docs:json && build-storybook" }, "keywords": [], "author": "FZJ-INM1-BDA <inm1-bda@fz-juelich.de>", @@ -23,18 +24,31 @@ "@angular-devkit/build-angular": "^12.2.13", "@angular/cli": "^12.2.13", "@angular/compiler-cli": "^12.2.13", + "@babel/core": "^7.17.5", + "@compodoc/compodoc": "^1.1.19", + "@storybook/addon-actions": "^6.4.22", + "@storybook/addon-essentials": "^6.4.22", + "@storybook/addon-interactions": "^6.4.22", + "@storybook/addon-links": "^6.4.22", + "@storybook/angular": "^6.4.22", + "@storybook/builder-webpack5": "^6.4.22", + "@storybook/manager-webpack5": "^6.4.22", + "@storybook/testing-library": "0.0.11", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "^4.29.2", "@typescript-eslint/parser": "^4.29.2", + "babel-loader": "^8.2.3", "eslint": "^7.32.0", + "eslint-plugin-storybook": "^0.5.11", "jasmine-core": "~3.8.0", "jasmine-marbles": "^0.8.3", - "karma": "^6.3.16", + "karma": "^6.3.17", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.0.3", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "~1.7.0", + "storybook-dark-mode": "^1.0.9", "typescript": "~4.3.5" }, "dependencies": { @@ -53,7 +67,6 @@ "acorn": "^8.4.1", "export-nehuba": "0.0.12", "file-loader": "^6.2.0", - "hbp-connectivity-component": "^0.5.2", "jszip": "^3.6.0", "postcss": "^8.3.6", "raw-loader": "^4.0.2", diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..51c31a21157e2a010baa88039032c69d0d01ec2f --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,10 @@ +export { + JRPCRequest, + JRPCResp, + JRPCSuccessResp, + JRPCErrorResp, +} from "./jsonrpc" + +export { + ApiService, +} from "./service" diff --git a/src/api/jsonrpc.ts b/src/api/jsonrpc.ts new file mode 100644 index 0000000000000000000000000000000000000000..3c79f42a0132f6c89d92ec55f29872fd1826fcf9 --- /dev/null +++ b/src/api/jsonrpc.ts @@ -0,0 +1,111 @@ +type JRPCBase = { jsonrpc: "2.0" } + +export type JRPCRequest<Method, T> = { + method: Method // does NOT start with rpc. + params?: T + id?: string // if absent, notification, does not require response +} & JRPCBase + +export type JRPCSuccessResp<T> = { + result: T + id?: string +} & JRPCBase + +export type JRPCErrorResp<T> = { + error: { + /** + * + * -32700 Parse error Invalid JSON was received by the server.An error occurred on the server while parsing the JSON text. + * -32600 Invalid Request The JSON sent is not a valid Request object. + * -32601 Method not found The method does not exist / is not available. + * -32602 Invalid params Invalid method parameter(s). + * -32603 Internal error Internal JSON-RPC error. + * -32000 to -32099 Server error Reserved for implementation-defined server-errors. + */ + code: number + message: string + data?: T + } +} & JRPCBase + +export type JRPCResp<T, E> = JRPCSuccessResp<T> | JRPCErrorResp<E> + +export interface ListenerChannel { + notify: (payload: JRPCRequest<unknown, unknown>) => void + registerLeaveCb: (cb: () => void) => void +} + +export type BroadcastChannel< + Protocols extends Record<string, unknown>, +> = { + state: Protocols + listeners: ListenerChannel[] + emit: (event: keyof Protocols, payload: Protocols[keyof Protocols]) => void + addListener: (listener: ListenerChannel) => void +} + +export function createBroadcastingJsonRpcChannel< + NameSpace extends string, + Protocols extends Record<keyof Protocols, unknown> +>(namespace: NameSpace, defaultState: Protocols): BroadcastChannel<Protocols>{ + return { + state: defaultState, + listeners: [], + emit(event: keyof Protocols, value: Protocols[keyof Protocols]) { + const ev = `${namespace}${event as string}` + this.state[event] = value + const payload: Omit<JRPCRequest<string, Protocols[keyof Protocols]>, 'id'> = { + jsonrpc: '2.0', + method: ev, + params: this.state[event] + } + for (const listener of (this.listeners as ListenerChannel[])) { + listener.notify(payload) + } + }, + addListener(listener: ListenerChannel){ + if (this.listeners.indexOf(listener) < 0) { + this.listeners.push(listener) + } + listener.registerLeaveCb(() => { + this.listeners = this.listeners.filter(l => l !== listener) + }) + for (const key in this.state) { + const payload: Omit<JRPCRequest<string, Protocols[keyof Protocols]>,'id'> = { + jsonrpc: '2.0', + method: `${namespace}.${key}`, + params: this.state[key] + } + listener.notify(payload) + } + } + } +} + +type BoothProtocol = Record<string, { + request: unknown + response: unknown +}> + +export class BoothVisitor<T extends BoothProtocol>{ + constructor(private booth: Booth<T>){ + + } + request(event: JRPCRequest<keyof T, T[keyof T]['request']>) { + return this.booth.responder.onRequest(event) + } +} + +export interface BoothResponder<RespParam extends BoothProtocol>{ + onRequest: (event: JRPCRequest<keyof RespParam, RespParam[keyof RespParam]['request']>) => Promise<void | JRPCResp<RespParam[keyof RespParam]['response'], string>> +} + +export class Booth<T extends BoothProtocol>{ + constructor( + public responder: BoothResponder<T> + ){ + } + handshake() { + return new BoothVisitor<T>(this) + } +} diff --git a/src/api/service.ts b/src/api/service.ts new file mode 100644 index 0000000000000000000000000000000000000000..67269b96039a13ff4f246fea991a7b8adecdba8a --- /dev/null +++ b/src/api/service.ts @@ -0,0 +1,542 @@ +import { Inject, Injectable, Optional } from "@angular/core"; +import { select, Store } from "@ngrx/store"; +import { Subject } from "rxjs"; +import { distinctUntilChanged, filter, map, take } from "rxjs/operators"; +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel, OpenMINDSCoordinatePoint } from "src/atlasComponents/sapi"; +import { SxplrCoordinatePointExtension } from "src/atlasComponents/sapi/type"; +import { MainState, atlasSelection, userInteraction, annotation, atlasAppearance } from "src/state" +import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; +import { CANCELLABLE_DIALOG, CANCELLABLE_DIALOG_OPTS } from "src/util/interfaces"; +import { Booth, BoothResponder, createBroadcastingJsonRpcChannel, JRPCRequest, JRPCResp } from "./jsonrpc" + +export type NAMESPACE_TYPE = "sxplr" +export const namespace: NAMESPACE_TYPE = "sxplr" +const nameSpaceRegex = new RegExp(`^${namespace}`) + +type AddableLayer = atlasAppearance.NgLayerCustomLayer + +type AtId = { + "@id": string +} + +type RequestUserTypes = { + region: SapiRegionModel + point: OpenMINDSCoordinatePoint + confirm: void + input: string +} + +type RequestUser<T extends keyof RequestUserTypes> = { + type: T + message: string + promise: Promise<RequestUserTypes[T]> + id: string + rs: (arg: RequestUserTypes[T]) => void + rj: (reason: string) => void +} + +export type ApiBoothEvents = { + getAllAtlases: { + request: null + response: SapiAtlasModel[] + } + getSupportedTemplates: { + request: null + response: SapiSpaceModel[] + } + getSupportedParcellations: { + request: null + response: SapiParcellationModel[] + } + + selectAtlas: { + request: AtId + response: 'OK' + } + selectParcellation: { + request: AtId + response: 'OK' + } + selectTemplate: { + request: AtId + response: 'OK' + } + + navigateTo: { + request: MainState['[state.atlasSelection]']['navigation'] & { animate?: boolean } + response: 'OK' + } + + getUserToSelectARoi: { + request: { + type: 'region' | 'point' + message: string + } + response: SapiRegionModel | OpenMINDSCoordinatePoint + } + + addAnnotations: { + request: { + annotations: SxplrCoordinatePointExtension[] + } + response: 'OK' + } + + rmAnnotations: { + request: { + annotations: AtId[] + } + response: 'OK' + } + + loadLayers: { + request: { + layers: AddableLayer[] + } + response: 'OK' + } + + updateLayers: { + request: { + layers: AddableLayer[] + } + response: 'OK' + } + + removeLayers: { + request: { + layers: {id: string}[] + } + response: 'OK' + } + + exit: { + request: { + requests: JRPCRequest<keyof ApiBoothEvents, ApiBoothEvents[keyof ApiBoothEvents]['request']>[] + } + response: 'OK' + } + + cancelRequest: { + request: { + id: string + } + response: 'OK' + } +} + +export type HeartbeatEvents = { + init: { + request: null + response: { + name: string + } + } +} + +export type BroadCastingApiEvents = { + atlasSelected: SapiAtlasModel + templateSelected: SapiSpaceModel + parcellationSelected: SapiParcellationModel + allRegions: SapiRegionModel[] + regionsSelected: SapiRegionModel[] +} + +const broadCastDefault: BroadCastingApiEvents = { + atlasSelected: null, + templateSelected: null, + parcellationSelected: null, + allRegions: [], + regionsSelected: [], +} + +@Injectable({ + providedIn: 'root' +}) + +export class ApiService implements BoothResponder<ApiBoothEvents>{ + + public broadcastCh = createBroadcastingJsonRpcChannel<`${NAMESPACE_TYPE}.on`, BroadCastingApiEvents>(`${namespace}.on`, broadCastDefault) + public booth = new Booth<ApiBoothEvents>(this) + + private requestUserQueue: RequestUser<keyof RequestUserTypes>[] = [] + private requestUser$ = new Subject<RequestUser<keyof RequestUserTypes>>() + private fulfillUserRequest(error: string, result: RequestUserTypes[keyof RequestUserTypes]){ + const { + rs, rj + } = this.requestUserQueue.pop() + if (!!error) { + rj(error) + } else { + rs(result) + } + if (this.dismissDialog) { + this.dismissDialog() + this.dismissDialog = null + } + if (this.requestUserQueue.length > 0) { + this.requestUser$.next(this.requestUserQueue[0]) + } + } + private dismissDialog: () => void + private onMouseClick(): boolean { + if (this.requestUserQueue.length === 0) return true + + const { type } = this.requestUserQueue[0] + + if (type === "region") { + let moRegion: SapiRegionModel + this.store.pipe( + select(userInteraction.selectors.mousingOverRegions), + filter(val => val.length > 0), + map(val => val[0]), + take(1) + ).subscribe(region => moRegion = region) + if (!!moRegion) { + this.fulfillUserRequest(null, moRegion) + return false + } + } + + if (type === "point") { + let point: OpenMINDSCoordinatePoint + this.store.pipe( + select(userInteraction.selectors.mousingOverPosition), + take(1) + ).subscribe(p => point = p) + if (!!point) { + this.fulfillUserRequest(null, point) + return false + } + } + return true + } + + private onDestoryCb: (() => void)[] = [] + constructor( + private store: Store, + private sapi: SAPI, + @Optional() @Inject(CANCELLABLE_DIALOG) openCancellableDialog: (message: string, options: CANCELLABLE_DIALOG_OPTS) => () => void, + @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor, + ){ + + if (clickInterceptor) { + const { register, deregister } = clickInterceptor + const onMouseClick = this.onMouseClick.bind(this) + register(onMouseClick) + this.onDestoryCb.push(() => deregister(onMouseClick)) + } + + if (openCancellableDialog) { + + const requestUsersSub = this.requestUser$.pipe( + distinctUntilChanged((o, n) => o?.promise === n?.promise) + ).subscribe(item => { + if (this.dismissDialog) this.dismissDialog() + if (!item) return + this.dismissDialog = openCancellableDialog(item.message, { + userCancelCallback: () => { + this.fulfillUserRequest(`user Cancelled`, null) + this.dismissDialog = null + } + }) + }) + this.onDestoryCb.push(() => requestUsersSub.unsubscribe()) + } + + this.store.pipe( + select(atlasSelection.selectors.selectedAtlas) + ).subscribe(atlas => { + this.broadcastCh.emit('atlasSelected', atlas) + }) + this.store.pipe( + select(atlasSelection.selectors.selectedParcellation) + ).subscribe(parcellation => { + this.broadcastCh.emit('parcellationSelected', parcellation) + }) + this.store.pipe( + select(atlasSelection.selectors.selectedTemplate) + ).subscribe(template => { + this.broadcastCh.emit('templateSelected', template) + }) + this.store.pipe( + select(atlasSelection.selectors.selectedRegions) + ).subscribe(regions => { + this.broadcastCh.emit('regionsSelected', regions) + }) + this.store.pipe( + select(atlasSelection.selectors.selectedParcAllRegions) + ).subscribe(regions => { + this.broadcastCh.emit('allRegions', regions) + }) + } + async onRequest(event: JRPCRequest<keyof ApiBoothEvents, unknown>): Promise<void | JRPCResp<ApiBoothEvents[keyof ApiBoothEvents]['response'], string>> { + /** + * if id is not present, then it's a no-op + */ + if (!event.id) { + return + } + if (!nameSpaceRegex.test(event.method)) return + + const method = event.method.replace(nameSpaceRegex, '').replace(/^\./, '') + switch (method) { + case 'getAllAtlases': { + if (!event.id) return + const atlases = await this.sapi.atlases$.pipe( + take(1) + ).toPromise() + return { + id: event.id, + result: atlases, + jsonrpc: '2.0' + } + } + case 'getSupportedParcellations': { + if (!event.id) return + const parcs = await this.store.pipe( + atlasSelection.fromRootStore.allAvailParcs(this.sapi), + take(1) + ).toPromise() + return { + id: event.id, + jsonrpc: '2.0', + result: parcs + } + } + case 'getSupportedTemplates': { + if (!event.id) return + const spaces = await this.store.pipe( + atlasSelection.fromRootStore.allAvailSpaces(this.sapi), + take(1) + ).toPromise() + return { + id: event.id, + jsonrpc: '2.0', + result: spaces + } + } + case 'selectAtlas': { + const atlases = await this.sapi.atlases$.pipe( + take(1) + ).toPromise() + const id = event.params as ApiBoothEvents['selectAtlas']['request'] + const atlas = atlases.find(atlas => atlas["@id"] === id?.["@id"]) + if (!atlas) { + if (!!event.id) { + return { + id: event.id, + jsonrpc: '2.0', + error: { + code: -32602, + message:`atlas id ${id?.["@id"]} not found` + } + } + } + return + } + this.store.dispatch( + atlasSelection.actions.selectAtlas({ atlas }) + ) + if (!!event.id) { + return { + jsonrpc: '2.0', + id: event.id, + result: null + } + } + break + } + case 'selectParcellation': { + if (!!event.id) { + return { + jsonrpc: '2.0', + id: event.id, + error: { + code: -32601, + message: `NYI` + } + } + } + break + } + case 'selectTemplate': { + if (!!event.id) { + return { + jsonrpc: '2.0', + id: event.id, + error: { + code: -32601, + message: `NYI` + } + } + } + break + } + case 'navigateTo': { + const { animate, ...navigation } = event.params as ApiBoothEvents['navigateTo']['request'] + this.store.dispatch( + atlasSelection.actions.navigateTo({ + navigation, + animation: !!animate + }) + ) + if (!!event.id) { + const timeoutDuration = !!animate + ? 500 + : 0 + await new Promise(rs => setTimeout(rs, timeoutDuration)) + return { + id: event.id, + jsonrpc: '2.0', + result: null + } + } + break + } + case 'getUserToSelectARoi': { + const { params, id } = event as JRPCRequest<'getUserToSelectARoi', ApiBoothEvents['getUserToSelectARoi']['request']> + const { type, message } = params + if (!params || (type !== "region" && type !== "point")) { + return { + id: event.id, + jsonrpc: '2.0', + error: { + code: -32602, + message: `type must be either region or point!` + } + } + } + let rs, rj + const promise = new Promise<RequestUserTypes['region'] | RequestUserTypes['point']>((_rs, _rj) => { + rs = _rs + rj = _rj + }) + this.requestUserQueue.push({ + message, + promise, + id, + type: type as 'region' | 'point', + rj, + rs + }) + this.requestUser$.next( + this.requestUserQueue[0] + ) + return promise.then(val => { + return { + id, + jsonrpc: '2.0', + result: val + } + }) + } + case 'addAnnotations': { + const { annotations } = event.params as ApiBoothEvents['addAnnotations']['request'] + const ann = annotations as (annotation.Annotation<'openminds'>)[] + this.store.dispatch( + annotation.actions.addAnnotations({ + annotations: ann + }) + ) + if (event.id) { + return { + jsonrpc: '2.0', + id: event.id, + result: 'OK' + } + } + break + } + case 'rmAnnotations': { + const { annotations } = event.params as ApiBoothEvents['rmAnnotations']['request'] + this.store.dispatch( + annotation.actions.rmAnnotations({ + annotations + }) + ) + if (event.id){ + return { + jsonrpc: '2.0', + id: event.id, + result: 'OK' + } + } + break + } + case 'loadLayers': + case 'updateLayers': { + const { layers } = event.params as ApiBoothEvents['loadLayers']['request'] | ApiBoothEvents['updateLayers']['request'] + for (const layer of layers) { + this.store.dispatch( + atlasAppearance.actions.addCustomLayer({ + customLayer: layer + }) + ) + } + break + } + case 'removeLayers': { + const { layers } = event.params as ApiBoothEvents['removeLayers']['request'] + for (const layer of layers) { + this.store.dispatch( + atlasAppearance.actions.removeCustomLayer(layer) + ) + } + break + } + case 'exit': { + const { requests } = event.params as ApiBoothEvents['exit']['request'] + for (const req of requests) { + await this.onRequest(req) + } + break + } + case 'cancelRequest': { + const { id } = event.params as ApiBoothEvents['cancelRequest']['request'] + const idx = this.requestUserQueue.findIndex(q => q.id === id) + if (idx < 0) { + if (!!event.id) { + return { + jsonrpc: '2.0', + id: event.id, + error: { + code: -1, + message: `cancelRequest failed, request with id ${id} does not exist, or has already been resolved.` + } + } + } + return + } + const req = this.requestUserQueue.splice(idx, 1) + req[0].rj(`client cancelled`) + + this.requestUser$.next( + this.requestUserQueue[0] + ) + + if (!!event.id) { + return { + jsonrpc: '2.0', + id: event.id, + result: null + } + } + break + } + default: { + const message = `Method ${event.method} not found.` + if (!!event.id) { + return { + jsonrpc: '2.0', + id: event.id, + error: { + code: -32601, + message + } + } + } + } + } + } +} diff --git a/src/assets/images/atlas-selection/firbe-long.png b/src/assets/images/atlas-selection/fibre-long.png similarity index 100% rename from src/assets/images/atlas-selection/firbe-long.png rename to src/assets/images/atlas-selection/fibre-long.png diff --git a/src/assets/images/atlas-selection/firbe-short.png b/src/assets/images/atlas-selection/fibre-short.png similarity index 100% rename from src/assets/images/atlas-selection/firbe-short.png rename to src/assets/images/atlas-selection/fibre-short.png diff --git a/src/atlasComponents/annotations/annotation.service.ts b/src/atlasComponents/annotations/annotation.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd67601ee087cf32ddd230d7e42b3ca9fdaea421 --- /dev/null +++ b/src/atlasComponents/annotations/annotation.service.ts @@ -0,0 +1,204 @@ +import { BehaviorSubject, Observable } from "rxjs"; +import { distinctUntilChanged } from "rxjs/operators"; +import { getUuid } from "src/util/fn"; + +export type TNgAnnotationEv = { + pickedAnnotationId: string + pickedOffset: number +} + +/** + * axis aligned bounding box + */ +export type TNgAnnotationAABBox = { + type: 'aabbox' + pointA: [number, number, number] + pointB: [number, number, number] + id: string + description?: string +} + +export type TNgAnnotationLine = { + type: 'line' + pointA: [number, number, number] + pointB: [number, number, number] + id: string + description?: string +} + +export type TNgAnnotationPoint = { + type: 'point' + point: [number, number, number] + id: string + description?: string +} + +export type AnnotationSpec = TNgAnnotationLine | TNgAnnotationPoint | TNgAnnotationAABBox +type _AnnotationSpec = Omit<AnnotationSpec, 'type'> & { type: number } +type AnnotationRef = Record<string, unknown> + +interface NgAnnotationLayer { + layer: { + localAnnotations: { + references: { + get(id: string): AnnotationRef + delete(id: string): void + } + update(ref: AnnotationRef, spec: _AnnotationSpec): void + add(spec: _AnnotationSpec): void + delete(spec: AnnotationRef):void + annotationMap: Map<string, _AnnotationSpec> + } + registerDisposer(fn: () => void): void + } + setVisible(flag: boolean): void +} + +export class AnnotationLayer { + static Map = new Map<string, AnnotationLayer>() + static Get(name: string, color: string){ + if (AnnotationLayer.Map.has(name)) return AnnotationLayer.Map.get(name) + const layer = new AnnotationLayer(name, color) + AnnotationLayer.Map.set(name, layer) + return layer + } + + private _onHover = new BehaviorSubject<{ id: string, offset: number }>(null) + public onHover: Observable<{ id: string, offset: number }> = this._onHover.asObservable().pipe( + distinctUntilChanged((o, n) => o?.id === n?.id) + ) + private onDestroyCb: (() => void)[] = [] + private nglayer: NgAnnotationLayer + private idset = new Set<string>() + constructor( + private name: string = getUuid(), + private color="#ffffff" + ){ + const layerSpec = this.viewer.layerSpecification.getLayer( + this.name, + { + type: "annotation", + "annotationColor": this.color, + "annotations": [], + name: this.name, + transform: [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + ] + } + ) + this.nglayer = this.viewer.layerManager.addManagedLayer(layerSpec) + const mouseState = this.viewer.mouseState + const res: () => void = mouseState.changed.add(() => { + const payload = mouseState.active + && !!mouseState.pickedAnnotationId + && this.idset.has(mouseState.pickedAnnotationId) + ? { + id: mouseState.pickedAnnotationId, + offset: mouseState.pickedOffset + } + : null + this._onHover.next(payload) + }) + this.onDestroyCb.push(res) + + this.nglayer.layer.registerDisposer(() => { + this.dispose() + }) + } + setVisible(flag: boolean){ + this.nglayer && this.nglayer.setVisible(flag) + } + dispose() { + this.nglayer = null + AnnotationLayer.Map.delete(this.name) + this._onHover.complete() + while(this.onDestroyCb.length > 0) this.onDestroyCb.pop()() + try { + this.viewer.layerManager.removeManagedLayer(this.nglayer) + // eslint-disable-next-line no-empty + } catch (e) { + + } + } + + addAnnotation(spec: AnnotationSpec){ + if (!this.nglayer) { + throw new Error(`layer has already been disposed`) + } + const localAnnotations = this.nglayer.layer.localAnnotations + this.idset.add(spec.id) + const annSpec = this.parseNgSpecType(spec) + localAnnotations.add( + annSpec + ) + } + removeAnnotation(spec: { id: string }) { + if (!this.nglayer) return + const { localAnnotations } = this.nglayer.layer + this.idset.delete(spec.id) + const ref = localAnnotations.references.get(spec.id) + if (ref) { + localAnnotations.delete(ref) + localAnnotations.references.delete(spec.id) + } + } + updateAnnotation(spec: AnnotationSpec) { + const localAnnotations = this.nglayer?.layer?.localAnnotations + if (!localAnnotations) return + const ref = localAnnotations.references.get(spec.id) + const _spec = this.parseNgSpecType(spec) + if (ref) { + localAnnotations.update( + ref, + _spec + ) + } else { + this.idset.add(_spec.id) + localAnnotations.add(_spec) + } + } + + private get viewer() { + if ((window as any).viewer) return (window as any).viewer + throw new Error(`window.viewer not defined`) + } + + private parseNgSpecType(spec: AnnotationSpec): _AnnotationSpec{ + const voxelSize = this.viewer.navigationState.voxelSize.toJSON() + const sanitizePoint = (p: [number, number, number]) => p.map((v, idx) => v / voxelSize[idx]) as [number, number, number] + const needSanitizePosition = voxelSize[0] !== 1 || voxelSize[1] !== 1 || voxelSize[2] !== 1 + const overwrite: Partial<_AnnotationSpec> = {} + switch (spec.type) { + case "point": { + overwrite['type'] = 0 + break + } + case "line": { + overwrite['type'] = 1 + break + } + case "aabbox": { + overwrite['type'] = 2 + break + } + default: throw new Error(`overwrite type lookup failed for ${(spec as any).type}`) + } + + /** + * The unit of annotation(s) depends on voxel size. If it is 1,1,1 then it would be in um, but often it is not. + * If not sanitized, the annotation can be miles off. + */ + if (needSanitizePosition) { + for (const key of ['point', 'pointA', 'pointB'] ) { + if (!!spec[key]) overwrite[key] = sanitizePoint(spec[key]) + } + } + return { + ...spec, + ...overwrite, + } as _AnnotationSpec + } +} diff --git a/src/atlasComponents/annotations/index.ts b/src/atlasComponents/annotations/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..da271a6363a9f8e34165466841eba3dda31135f8 --- /dev/null +++ b/src/atlasComponents/annotations/index.ts @@ -0,0 +1 @@ +export { TNgAnnotationAABBox, AnnotationLayer, TNgAnnotationPoint, TNgAnnotationLine } from "./annotation.service" \ No newline at end of file diff --git a/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.style.css b/src/atlasComponents/annotations/module.ts similarity index 100% rename from src/atlasComponents/parcellationRegion/regionSimple/regionSimple.style.css rename to src/atlasComponents/annotations/module.ts diff --git a/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.component.ts b/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.component.ts deleted file mode 100644 index 638863a0893fc8a064fc1e7482b24b56bf956a39..0000000000000000000000000000000000000000 --- a/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.component.ts +++ /dev/null @@ -1,450 +0,0 @@ -import { - AfterViewInit, ChangeDetectorRef, - Component, - ElementRef, - EventEmitter, - OnDestroy, - Output, - ViewChild, - Input, - OnInit, Inject, -} from "@angular/core"; -import {select, Store} from "@ngrx/store"; -import {fromEvent, Observable, Subscription, Subject, combineLatest} from "rxjs"; -import {distinctUntilChanged, filter, map} from "rxjs/operators"; - -import { viewerStateNavigateToRegion, viewerStateSetSelectedRegions } from "src/services/state/viewerState.store.helper"; -import { ngViewerSelectorClearViewEntries, ngViewerActionClearView } from "src/services/state/ngViewerState.store.helper"; -import { - viewerStateAllRegionsFlattenedRegionSelector, - viewerStateOverwrittenColorMapSelector -} from "src/services/state/viewerState/selectors"; -import {HttpClient} from "@angular/common/http"; -import {BS_ENDPOINT} from "src/util/constants"; -import {getIdFromKgIdObj} from "common/util"; -import {OVERWRITE_SHOW_DATASET_DIALOG_TOKEN} from "src/util/interfaces"; - - -const CONNECTIVITY_NAME_PLATE = 'Connectivity' - -@Component({ - selector: 'connectivity-browser', - templateUrl: './connectivityBrowser.template.html', - providers: [ - { - provide: OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, - useValue: null - } - ] -}) -export class ConnectivityBrowserComponent implements OnInit, AfterViewInit, OnDestroy { - - private setColorMap$: Subject<boolean> = new Subject() - - /** - * accordion expansion should only toggle the clearviewqueue state - * which should be the single source of truth - * setcolormaps$ is set by the presence/absence of clearviewqueue[CONNECTIVITY_NAME_PLATE] - */ - private _isFirstUpdate = true - - public connectivityUrl: string - - private accordionIsExpanded = false - - @Input() - set accordionExpanded(flag: boolean) { - /** - * ignore first update - */ - if (this._isFirstUpdate) { - this._isFirstUpdate = false - return - } - this.accordionIsExpanded = flag - this.store$.dispatch( - ngViewerActionClearView({ - payload: { - [CONNECTIVITY_NAME_PLATE]: flag && !this.noDataReceived - } - }) - ) - this.store$.dispatch({ - type: 'SET_OVERWRITTEN_COLOR_MAP', - payload: flag? CONNECTIVITY_NAME_PLATE : false, - }) - - if (flag) { - this.addNewColorMap() - } else { - this.restoreDefaultColormap() - } - } - - @Output() - connectivityDataReceived = new EventEmitter<any>() - - @Output() - setOpenState: EventEmitter<boolean> = new EventEmitter() - - @Output() - connectivityLoadUrl: EventEmitter<string> = new EventEmitter() - - @Output() connectivityNumberReceived: EventEmitter<string> = new EventEmitter() - - @Input() - set region(val) { - const newRegionName = val && val.name - - if (!val) { - this.store$.dispatch({ - type: 'SET_OVERWRITTEN_COLOR_MAP', - payload: false, - }) - return - } - - if (newRegionName !== this.regionName && this.defaultColorMap) { - this.restoreDefaultColormap() - } - - if (val.status - && !val.name.includes('left hemisphere') - && !val.name.includes('right hemisphere')) { - this.regionHemisphere = val.status - } - - this.regionName = newRegionName - this.regionId = val.id? val.id.kg? getIdFromKgIdObj(val.id.kg) : val.id : null - this.atlasId = val.context.atlas['@id'] - this.parcellationId = val.context.parcellation['@id'] - - if(this.selectedDataset) { - this.setConnectivityUrl() - this.setProfileLoadUrl() - } - // TODO may not be necessary - this.changeDetectionRef.detectChanges() - } - public atlasId: any - public parcellationId: any - public regionId: string - public regionName: string - public regionHemisphere: string = null - public datasetList: any[] = [] - public selectedDataset: any - public selectedDatasetName: any - public selectedDatasetDescription: string = '' - public selectedDatasetKgId: string = '' - public selectedDatasetKgSchema: string = '' - public connectedAreas = [] - - private selectedParcellationFlatRegions$ = this.store$.pipe( - select(viewerStateAllRegionsFlattenedRegionSelector) - ) - public overwrittenColorMap$: Observable<any> - - private subscriptions: Subscription[] = [] - public expandMenuIndex = -1 - public allRegions = [] - public defaultColorMap: Map<string, Map<number, { red: number, green: number, blue: number }>> - - public noDataReceived = false - - @ViewChild('connectivityComponent', {read: ElementRef}) public connectivityComponentElement: ElementRef<any> - @ViewChild('fullConnectivityGrid') public fullConnectivityGridElement: ElementRef<any> - - constructor( - private store$: Store<any>, - private changeDetectionRef: ChangeDetectorRef, - private httpClient: HttpClient, - @Inject(BS_ENDPOINT) private siibraApiUrl: string, - ) { - - this.overwrittenColorMap$ = this.store$.pipe( - select(viewerStateOverwrittenColorMapSelector), - distinctUntilChanged() - ) - } - - public loadUrl: string - public fullConnectivityLoadUrl: string - - ngOnInit(): void { - this.setConnectivityUrl() - - this.httpClient.get<[]>(this.connectivityUrl).subscribe(res => { - this.datasetList = res - this.selectedDataset = this.datasetList[0]?.['@id'] - this.selectedDatasetName = this.datasetList[0]?.['src_name'] - this.selectedDatasetDescription = this.datasetList[0]?.['src_info'] - // this.selectedDatasetKgId = this.datasetList[0]?.kgId || null - // this.selectedDatasetKgSchema = this.datasetList[0]?.kgSchema || null - - this.changeDataset() - }) - } - - public ngAfterViewInit(): void { - this.subscriptions.push( - this.store$.pipe( - select(viewerStateOverwrittenColorMapSelector), - ).subscribe(value => { - if (this.accordionIsExpanded) { - this.setColorMap$.next(!!value) - } - }) - ) - - /** - * Listen to of clear view entries - * can come from within the component (when connectivity is not available for the dataset) - * --> do not collapse - * or outside (user clicks x in chip) - * --> collapse - */ - this.subscriptions.push( - this.store$.pipe( - select(ngViewerSelectorClearViewEntries), - map(arr => arr.filter(v => v === CONNECTIVITY_NAME_PLATE)), - filter(arr => arr.length ===0), - distinctUntilChanged() - ).subscribe(() => { - if (!this.noDataReceived) { - this.setOpenState.emit(false) - } - }) - ) - - - this.subscriptions.push(this.overwrittenColorMap$.subscribe(ocm => { - if (this.accordionIsExpanded && !ocm) { - this.setOpenState.emit(false) - } - })) - - this.subscriptions.push( - this.selectedParcellationFlatRegions$.subscribe(flattenedRegions => { - this.defaultColorMap = null - this.allRegions = flattenedRegions - }), - ) - - /** - * setting/restoring colormap - */ - this.subscriptions.push( - combineLatest( - this.setColorMap$.pipe( - distinctUntilChanged() - ), - fromEvent(this.connectivityComponentElement?.nativeElement, 'connectivityDataReceived').pipe( - map((e: CustomEvent) => { - if (e.detail !== 'No data') { - this.connectivityNumberReceived.emit(e.detail.length) - } - return e.detail - }) - ) - ).subscribe(([flag, connectedAreas]) => { - if (connectedAreas === 'No data') { - this.noDataReceived = true - return this.clearViewer() - } else { - this.store$.dispatch( - ngViewerActionClearView({ - payload: { - [CONNECTIVITY_NAME_PLATE]: true - } - }) - ) - this.noDataReceived = false - this.connectivityNumberReceived.emit(connectedAreas.length) - this.connectedAreas = connectedAreas - - if (flag) { - this.addNewColorMap() - this.store$.dispatch({ - type: 'SET_OVERWRITTEN_COLOR_MAP', - payload: 'connectivity', - }) - } else { - this.restoreDefaultColormap() - - this.store$.dispatch({type: 'SET_OVERWRITTEN_COLOR_MAP', payload: null}) - - } - } - }) - ) - - this.subscriptions.push( - fromEvent(this.connectivityComponentElement?.nativeElement, 'customToolEvent', {capture: true}) - .subscribe((e: CustomEvent) => { - if (e.detail.name === 'export csv') { - // ToDo Fix in future to use component - const a = document.querySelector('hbp-connectivity-matrix-row'); - (a as any).downloadCSV() - } - }), - fromEvent(this.connectivityComponentElement?.nativeElement, 'connectedRegionClicked', {capture: true}) - .subscribe((e: CustomEvent) => { - this.navigateToRegion(e.detail.name) - }), - ) - } - - public ngOnDestroy(): void { - this.connectivityNumberReceived.emit(null) - this.store$.dispatch( - ngViewerActionClearView({ - payload: { - [CONNECTIVITY_NAME_PLATE]: false - } - }) - ) - this.restoreDefaultColormap() - this.subscriptions.forEach(s => s.unsubscribe()) - } - - private setConnectivityUrl() { - this.connectivityUrl = `${this.siibraApiUrl}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.parcellationId)}/regions/${encodeURIComponent(this.regionName)}/features/ConnectivityProfile` - } - - private setProfileLoadUrl() { - const url = `${this.connectivityUrl}/${encodeURIComponent(this.selectedDataset)}` - this.connectivityLoadUrl.emit(url) - this.loadUrl = url - } - - clearViewer() { - this.store$.dispatch( - ngViewerActionClearView({ - payload: { - [CONNECTIVITY_NAME_PLATE]: false - } - }) - ) - this.connectedAreas = [] - this.connectivityNumberReceived.emit('0') - - return this.restoreDefaultColormap() - } - - // ToDo Affect on component - changeDataset(event = null) { - if (event) { - this.selectedDataset = event.value - const foundDataset = this.datasetList.find(d => d['@id'] === this.selectedDataset) - this.selectedDatasetName = foundDataset?.['src_name'] - this.selectedDatasetDescription = foundDataset?.['src_info'] - // this.selectedDatasetKgId = foundDataset?.kgId || null - // this.selectedDatasetKgSchema = foundDataset?.kgSchema || null - } - if (this.datasetList.length && this.selectedDataset) { - this.setProfileLoadUrl() - - this.fullConnectivityLoadUrl = `${this.siibraApiUrl}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.parcellationId)}/features/ConnectivityMatrix/${encodeURIComponent(this.selectedDatasetName)}` - } - } - - navigateToRegion(region) { - this.store$.dispatch( - viewerStateNavigateToRegion({ - payload: {region: this.getRegionWithName(region)} - }) - ) - } - - selectRegion(region) { - this.store$.dispatch( - viewerStateSetSelectedRegions({ - selectRegions: [ region ] - }) - ) - } - - getRegionWithName(region) { - return this.allRegions.find(ar => { - if (this.regionHemisphere) { - let regionName = region - let regionStatus = null - if (regionName.includes('left hemisphere')) { - regionStatus = 'left hemisphere' - regionName = regionName.replace(' - left hemisphere', ''); - } else if (regionName.includes('right hemisphere')) { - regionStatus = 'right hemisphere' - regionName = regionName.replace(' - right hemisphere', ''); - } - return ar.name === regionName && ar.status === regionStatus - } - - return ar.name === region - }) - } - - public restoreDefaultColormap() { - if (!this.defaultColorMap) return - getWindow().interactiveViewer.viewerHandle.applyLayersColourMap(this.defaultColorMap) - } - - public addNewColorMap() { - if (!this.defaultColorMap) { - this.defaultColorMap = getWindow().interactiveViewer.viewerHandle.getLayersSegmentColourMap() - } - - const existingMap: Map<string, Map<number, { red: number, green: number, blue: number }>> = (getWindow().interactiveViewer.viewerHandle.getLayersSegmentColourMap()) - const colorMap = new Map(existingMap) - - this.allRegions.forEach(r => { - if (r.ngId) { - colorMap.get(r.ngId).set(r.labelIndex, {red: 255, green: 255, blue: 255}) - } - }) - - this.connectedAreas.forEach(area => { - const areaAsRegion = this.allRegions - .filter(r => { - - if (this.regionHemisphere) { - let regionName = area.name - let regionStatus = null - if (regionName.includes('left hemisphere')) { - regionStatus = 'left hemisphere' - regionName = regionName.replace(' - left hemisphere', ''); - } else if (regionName.includes('right hemisphere')) { - regionStatus = 'right hemisphere' - regionName = regionName.replace(' - right hemisphere', ''); - } - return r.name === regionName && r.status === regionStatus - } - - return r.name === area.name - }) - .map(r => r) - - if (areaAsRegion && areaAsRegion.length && areaAsRegion[0].ngId) { - colorMap.get(areaAsRegion[0].ngId).set(areaAsRegion[0].labelIndex, { - red: area.color.r, - green: area.color.g, - blue: area.color.b - }) - } - }) - getWindow().interactiveViewer.viewerHandle.applyLayersColourMap(colorMap) - } - - exportConnectivityProfile() { - const a = document.querySelector('hbp-connectivity-matrix-row'); - (a as any).downloadCSV() - } - - public exportFullConnectivity() { - this.fullConnectivityGridElement?.nativeElement['downloadCSV']() - } - -} - -function getWindow(): any { - return window -} diff --git a/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.template.html b/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.template.html deleted file mode 100644 index 9dfdc054e09c6eba8ad1318a37cca2db792a8ef2..0000000000000000000000000000000000000000 --- a/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.template.html +++ /dev/null @@ -1,97 +0,0 @@ -<div class="w-100 h-100 d-block d-flex flex-column pb-2"> - <hbp-connectivity-matrix-row - (connectivityDataReceived)="connectivityDataReceived.emit($event)" - #connectivityComponent - *ngIf="regionName" - [region]="regionName + (regionHemisphere? ' - ' + regionHemisphere : '')" - theme="dark" - [loadurl]="loadUrl" - show-export="true" - show-source="false" - show-title="false" - show-toolbar="false" - show-description="false" - show-dataset-name="false" - custom-dataset-selector="true" - tools_showlog="true" - [tools_custom]='[{"name": "exportslot", "type": "slot"}]' - hide-export-view="true"> - - <div slot="dataset"> - <div *ngIf="datasetList.length && selectedDataset" class=" flex-grow-0 flex-shrink-0 d-flex flex-row flex-nowrap"> - <mat-form-field class="flex-grow-1 flex-shrink-1 w-0"> - <mat-label> - Dataset - </mat-label> - - <mat-select - panelClass="no-max-width" - [value]="selectedDataset" - (selectionChange)="changeDataset($event)"> - <mat-option - *ngFor="let dataset of datasetList" - [value]="dataset['@id']"> - {{ dataset['src_name'] }} - </mat-option> - </mat-select> - </mat-form-field> - <ng-container *ngIf="selectedDataset && (selectedDatasetDescription || selectedDatasetKgId)" > - <button class="flex-grow-0 flex-shrink-0" - mat-icon-button - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-name]="selectedDatasetName" - [iav-dataset-show-dataset-dialog-description]="selectedDatasetDescription" - [iav-dataset-show-dataset-dialog-kgid]="selectedDatasetKgId? selectedDatasetKgId : null" - [iav-dataset-show-dataset-dialog-kgschema]="selectedDatasetKgSchema? selectedDatasetKgSchema : null" - > - <i class="fas fa-info"></i> - </button> - <button class="flex-grow-0 flex-shrink-0" - mat-icon-button - (click)="exportFullConnectivity()" - matTooltip="Export full connectivity profile"> - <i class="fas fa-download"></i> - </button> - </ng-container> - </div> - <div> - Connectivity Profile - </div> - </div> - - <div slot="connectedRegionMenu"> - <div class="d-flex flex-column p-0 m-0" *ngIf="expandMenuIndex >= 0"> - <mat-card-subtitle class="pt-2 pr-2 pl-2 pb-0"> - <div class="d-flex justify-content-between align-items-center"> - {{connectedAreas[expandMenuIndex].name}} - <small class="d-flex align-items-center"> - <button mat-icon-button matTooltip="Navigate" - (click)="navigateToRegion(connectedAreas[expandMenuIndex].name)"> - <i class="fas fa-map-marked-alt"></i> - </button> - <button mat-icon-button matTooltip="Explore" - (click)="selectRegion(getRegionWithName(connectedAreas[expandMenuIndex].name))"> - <i class="fas fa-search-location"></i> - </button> - </small> - </div> - </mat-card-subtitle> - <mat-divider></mat-divider> - </div> - </div> - <div slot="exportslot"> - <button mat-icon-button - [disabled]="noDataReceived" - (click)="exportConnectivityProfile()" - matTooltip="Export connectivity profile"> - <i class="fas fa-download mb-2"></i> - </button> - </div> - </hbp-connectivity-matrix-row> - <full-connectivity-grid #fullConnectivityGrid - [loadurl]="fullConnectivityLoadUrl" - [name]="selectedDataset" - [description]="selectedDatasetDescription" - only-export="true"> - </full-connectivity-grid> -</div> diff --git a/src/atlasComponents/connectivity/hasConnectivity.directive.ts b/src/atlasComponents/connectivity/hasConnectivity.directive.ts deleted file mode 100644 index 8879de8c093488b90acd5fb81d57820f341da36e..0000000000000000000000000000000000000000 --- a/src/atlasComponents/connectivity/hasConnectivity.directive.ts +++ /dev/null @@ -1,62 +0,0 @@ -import {Directive, Inject, Input, OnDestroy, OnInit} from "@angular/core"; -import {of, Subscription} from "rxjs"; -import {switchMap} from "rxjs/operators"; -import {BS_ENDPOINT} from "src/util/constants"; -import {HttpClient} from "@angular/common/http"; - -@Directive({ - selector: '[has-connectivity]', - exportAs: 'hasConnectivityDirective' -}) - -export class HasConnectivity implements OnInit, OnDestroy { - - private subscriptions: Subscription[] = [] - - @Input() region: any - - public hasConnectivity = false - public connectivityNumber = 0 - - constructor(@Inject(BS_ENDPOINT) private siibraApiUrl: string, - private httpClient: HttpClient) {} - - ngOnInit() { - this.checkConnectivity(this.region[0]) - } - - checkConnectivity(region) { - if (!region.context) { - this.hasConnectivity = false - return - } - const {atlas, parcellation, template} = region.context - if (region.name) { - const connectivityUrl = `${this.siibraApiUrl}/atlases/${encodeURIComponent(atlas['@id'])}/parcellations/${encodeURIComponent(parcellation['@id'])}/regions/${encodeURIComponent(region.name)}/features/ConnectivityProfile` - - this.subscriptions.push( - this.httpClient.get<[]>(connectivityUrl).pipe(switchMap((res: any[]) => { - if (res && res.length) { - this.hasConnectivity = true - const url = `${connectivityUrl}/${encodeURIComponent(res[0]['@id'])}` - return this.httpClient.get(url) - } else { - this.hasConnectivity = false - this.connectivityNumber = 0 - } - return of(null) - })).subscribe(res => { - - if (res && res['__profile']) { - this.connectivityNumber = res['__profile'].filter(p => p > 0).length - } - }) - ) - } - } - - ngOnDestroy(){ - while (this.subscriptions.length > 0) this.subscriptions.pop().unsubscribe() - } - -} diff --git a/src/atlasComponents/connectivity/module.ts b/src/atlasComponents/connectivity/module.ts deleted file mode 100644 index a43e558c21da18e5998646bf63578f6ea00d8c28..0000000000000000000000000000000000000000 --- a/src/atlasComponents/connectivity/module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; -import { AngularMaterialModule } from "src/sharedModules"; -import { ConnectivityBrowserComponent } from "./connectivityBrowser/connectivityBrowser.component"; -import {HasConnectivity} from "src/atlasComponents/connectivity/hasConnectivity.directive"; -import {KgDatasetModule} from "src/atlasComponents/regionalFeatures/bsFeatures/kgDataset"; - -@NgModule({ - imports: [ - CommonModule, - KgDatasetModule, - AngularMaterialModule - ], - declarations: [ - ConnectivityBrowserComponent, - HasConnectivity - ], - exports: [ - ConnectivityBrowserComponent, - HasConnectivity - ], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA, - ], -}) - -export class AtlasCmptConnModule{} diff --git a/src/atlasComponents/parcellation/index.ts b/src/atlasComponents/parcellation/index.ts deleted file mode 100644 index f49438e683dafb818113eb571433a364854eb24b..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellation/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { GetParcPreviewUrlPipe } from "./getParcPreviewUrl.pipe"; -export { FilterNameBySearch } from "./regionHierachy/filterNameBySearch.pipe"; -export { AtlasCmpParcellationModule } from "./module"; -export { RegionHierarchy } from "./regionHierachy/regionHierarchy.component"; -export { RegionTextSearchAutocomplete } from "./regionSearch/regionSearch.component"; diff --git a/src/atlasComponents/parcellation/module.ts b/src/atlasComponents/parcellation/module.ts deleted file mode 100644 index c3e101c3a1d4bf3b2a6384723d79414e59a76429..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellation/module.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { AngularMaterialModule } from "src/sharedModules"; -import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; -import { RegionHierarchy } from "./regionHierachy/regionHierarchy.component"; -import { RegionTextSearchAutocomplete } from "./regionSearch/regionSearch.component"; -import { FilterNameBySearch } from "./regionHierachy/filterNameBySearch.pipe"; -import { UtilModule } from "src/util"; -import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { ComponentsModule } from "src/components"; -import { GetParcPreviewUrlPipe } from "./getParcPreviewUrl.pipe"; - -@NgModule({ - imports: [ - CommonModule, - UtilModule, - FormsModule, - ReactiveFormsModule, - AngularMaterialModule, - ParcellationRegionModule, - ComponentsModule, - ], - declarations: [ - RegionHierarchy, - RegionTextSearchAutocomplete, - - FilterNameBySearch, - GetParcPreviewUrlPipe, - ], - exports: [ - RegionHierarchy, - RegionTextSearchAutocomplete, - FilterNameBySearch, - GetParcPreviewUrlPipe, - ] -}) -export class AtlasCmpParcellationModule{} \ No newline at end of file diff --git a/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.component.ts b/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.component.ts deleted file mode 100644 index 715f000757552e41ee4e67ace06e9de56006e69e..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.component.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, ViewChild } from "@angular/core"; -import { fromEvent, Subject, Subscription } from "rxjs"; -import { buffer, debounceTime } from "rxjs/operators"; -import { FilterNameBySearch } from "./filterNameBySearch.pipe"; -import { serialiseParcellationRegion } from "common/util" - -const insertHighlight: (name: string, searchTerm: string) => string = (name: string, searchTerm: string = '') => { - const regex = new RegExp(searchTerm, 'gi') - return searchTerm === '' ? - name : - name.replace(regex, (s) => `<span class = "highlight">${s}</span>`) -} - -const getDisplayTreeNode: (searchTerm: string, selectedRegions: any[]) => (item: any) => string = (searchTerm: string = '', selectedRegions: any[] = []) => ({ ngId, name, status, labelIndex }) => { - return !!labelIndex - && !!ngId - && selectedRegions.findIndex(re => - serialiseParcellationRegion({ labelIndex: re.labelIndex, ngId: re.ngId }) === serialiseParcellationRegion({ ngId, labelIndex }), - ) >= 0 - ? `<span class="cursor-default regionSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``) - : `<span class="cursor-default regionNotSelected">${insertHighlight(name, searchTerm)}</span>` + (status ? ` <span class="text-muted">(${insertHighlight(status, searchTerm)})</span>` : ``) -} - -const getFilterTreeBySearch = (pipe: FilterNameBySearch, searchTerm: string) => - (node: any) => { - const searchFields = [ - node.name, - node.status, - ...(node.relatedAreas ? node.relatedAreas : []) - ] - return pipe.transform(searchFields, searchTerm) - } - -@Component({ - selector: 'region-hierarchy', - templateUrl: './regionHierarchy.template.html', - styleUrls: [ - './regionHierarchy.style.css', - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) - -export class RegionHierarchy implements OnInit, AfterViewInit { - - @Input() - public useMobileUI: boolean = false - - @Input() - public selectedRegions: any[] = [] - - @Input() - public parcellationSelected: any - - private _showRegionTree: boolean = false - - @Output() - private showRegionFlagChanged: EventEmitter<boolean> = new EventEmitter() - - @Output() - private singleClickRegion: EventEmitter<any> = new EventEmitter() - - @Output() - private doubleClickRegion: EventEmitter<any> = new EventEmitter() - - @Output() - private clearAllRegions: EventEmitter<MouseEvent> = new EventEmitter() - - public searchTerm: string = '' - private subscriptions: Subscription[] = [] - - @ViewChild('searchTermInput', {read: ElementRef}) - private searchTermInput: ElementRef - - public placeHolderText: string = `Start by selecting a template and a parcellation.` - - /** - * set the height to max, bound by max-height - */ - public numTotalRenderedRegions: number = 999 - public windowHeight: number - - @HostListener('document:click', ['$event']) - public closeRegion(event: MouseEvent) { - const contains = this.el.nativeElement.contains(event.target) - this.showRegionTree = contains - if (!this.showRegionTree) { - this.searchTerm = '' - this.numTotalRenderedRegions = 999 - } - } - - @HostListener('window:resize', ['$event']) - public onResize(event) { - this.windowHeight = event.target.innerHeight; - } - - get regionsLabelIndexMap() { - return null - } - - constructor( - private cdr: ChangeDetectorRef, - private el: ElementRef, - ) { - this.windowHeight = window.innerHeight; - } - - public ngOnChanges() { - if (this.parcellationSelected) { - this.placeHolderText = `Search region in ${this.parcellationSelected.name}` - this.aggregatedRegionTree = { - name: this.parcellationSelected.name, - children: this.parcellationSelected.regions, - } - } - this.displayTreeNode = getDisplayTreeNode(this.searchTerm, this.selectedRegions) - this.filterTreeBySearch = getFilterTreeBySearch(this.filterNameBySearchPipe, this.searchTerm) - } - - public clearRegions(event: MouseEvent) { - this.clearAllRegions.emit(event) - } - - get showRegionTree() { - return this._showRegionTree - } - - set showRegionTree(flag: boolean) { - this._showRegionTree = flag - this.showRegionFlagChanged.emit(this._showRegionTree) - } - - public ngOnInit() { - this.displayTreeNode = getDisplayTreeNode(this.searchTerm, this.selectedRegions) - this.filterTreeBySearch = getFilterTreeBySearch(this.filterNameBySearchPipe, this.searchTerm) - - this.subscriptions.push( - this.handleRegionTreeClickSubject.pipe( - buffer( - this.handleRegionTreeClickSubject.pipe( - debounceTime(200), - ), - ), - ).subscribe(arr => arr.length > 1 ? this.doubleClick(arr[0]) : this.singleClick(arr[0])), - ) - } - - public ngAfterViewInit() { - this.subscriptions.push( - fromEvent(this.searchTermInput.nativeElement, 'input').pipe( - debounceTime(200), - ).subscribe(ev => { - this.changeSearchTerm(ev) - }), - ) - } - - public escape(event: KeyboardEvent) { - this.showRegionTree = false - this.searchTerm = ''; - (event.target as HTMLInputElement).blur() - - } - - public handleTotalRenderedListChanged(changeEvent: {previous: number, current: number}) { - const { current } = changeEvent - this.numTotalRenderedRegions = current - } - - public regionHierarchyHeight() { - return({ - 'height' : (this.numTotalRenderedRegions * 15 + 60).toString() + 'px', - 'max-height': (this.windowHeight - 100) + 'px', - }) - } - - /* NB need to bind two way data binding like this. Or else, on searchInput blur, the flat tree will be rebuilt, - resulting in first click to be ignored */ - - public changeSearchTerm(event: any) { - if (event.target.value === this.searchTerm) { return } - this.searchTerm = event.target.value - this.ngOnChanges() - this.cdr.markForCheck() - } - - private handleRegionTreeClickSubject: Subject<any> = new Subject() - - public handleClickRegion(obj: any) { - const {event} = obj - /** - * TODO figure out why @closeRegion gets triggered, but also, contains returns false - */ - if (event) { - event.stopPropagation() - } - this.handleRegionTreeClickSubject.next(obj) - } - - /* single click selects/deselects region(s) */ - private singleClick(obj: any) { - if (!obj) { return } - const { inputItem : region } = obj - if (!region) { return } - this.singleClickRegion.emit(region) - } - - /* double click navigate to the interested area */ - private doubleClick(obj: any) { - if (!obj) { - return - } - const { inputItem : region } = obj - if (!region) { - return - } - this.doubleClickRegion.emit(region) - } - - public displayTreeNode: (item: any) => string - - private filterNameBySearchPipe = new FilterNameBySearch() - public filterTreeBySearch: (node: any) => boolean - - public aggregatedRegionTree: any - - public gotoRegion(region: any) { - this.doubleClickRegion.emit(region) - } - - public deselectRegion(region: any) { - this.singleClickRegion.emit(region) - } -} - -export function trackRegionBy(index: number, region: any) { - return region.labelIndex || region.id -} diff --git a/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.style.css b/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.style.css deleted file mode 100644 index 480bb3e62be1e9d1014bccb4fca0c84340ef0401..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.style.css +++ /dev/null @@ -1,64 +0,0 @@ - -div[treeContainer] -{ - padding:1em; - z-index: 3; - - height:20em; - overflow-y:auto; - overflow-x:hidden; - - /* color:white; - background-color:rgba(12,12,12,0.8); */ -} - -.flex-basis-33-pc -{ - flex-basis: 33%; -} - -.flex-basis-auto -{ - flex-basis: auto; -} - -input[type="text"] -{ - border:none; -} - - -.regionSearch -{ - width:20em; -} - -.tree-body -{ - flex: 1 1 auto; -} - -:host -{ - display: flex; - flex-direction: column; -} - -:host > mat-form-field -{ - flex: 0 0 auto; -} - -.horizontal-mode .cdk-viewport-wrapper -{ - display: flex; - flex-direction: row; - flex-wrap: nowrap; - - height:4rem; -} - -.cdk-virtual-scroll-viewport-container.horizontal-mode -{ - height: 6rem; -} \ No newline at end of file diff --git a/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.template.html b/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.template.html deleted file mode 100644 index f78b046b16046e4a0cc50c49a59031eb5811a163..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellation/regionHierachy/regionHierarchy.template.html +++ /dev/null @@ -1,41 +0,0 @@ -<mat-form-field class="w-100"> - <input - #searchTermInput - matInput - (keydown.esc)="escape($event)" - (focus)="showRegionTree = true" - [value]="searchTerm" - type="text" - autocomplete="off" - [placeholder]="placeHolderText"/> -</mat-form-field> - -<div - [ngClass]="{'flex-column': useMobileUI, 'flex-row': !useMobileUI}" - class="d-flex flex-grow-1 flex-shrink-1"> - - - <!-- region tree --> - <div class="flex-grow-1 flex-shrink-1 overflow-hidden"> - <div - class="d-flex flex-column h-100" - treeContainer - hideScrollbarInnerContainer - #treeContainer> - - <div - *ngIf="parcellationSelected && parcellationSelected.regions as regions" - class="tree-body"> - <flat-tree-component - (treeNodeClick)="handleClickRegion($event)" - (totalRenderedListChanged)="handleTotalRenderedListChanged($event)" - [inputItem]="aggregatedRegionTree" - [renderNode]="displayTreeNode" - [searchFilter]="filterTreeBySearch"> - - </flat-tree-component> - </div> - </div> - </div> -</div> - \ No newline at end of file diff --git a/src/atlasComponents/parcellation/regionSearch/regionSearch.component.ts b/src/atlasComponents/parcellation/regionSearch/regionSearch.component.ts deleted file mode 100644 index 6ff6301a2f59a77a959774d31299a679c317bed1..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellation/regionSearch/regionSearch.component.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, TemplateRef, ViewChild } from "@angular/core"; -import { FormControl } from "@angular/forms"; -import { select, Store } from "@ngrx/store"; -import { combineLatest, Observable, Subject, merge } from "rxjs"; -import { debounceTime, distinctUntilChanged, filter, map, shareReplay, startWith, take, tap, withLatestFrom } from "rxjs/operators"; -import { VIEWER_STATE_ACTION_TYPES } from "src/services/effect/effect"; -import { CHANGE_NAVIGATION, SELECT_REGIONS } from "src/services/state/viewerState.store"; -import { getMultiNgIdsRegionsLabelIndexMap } from "src/services/stateStore.service"; -import { LoggingService } from "src/logging"; -import { MatDialog } from "@angular/material/dialog"; -import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete"; -import { PureContantService } from "src/util"; -import { viewerStateToggleRegionSelect, viewerStateNavigateToRegion, viewerStateSetSelectedRegions, viewerStateSetSelectedRegionsWithIds } from "src/services/state/viewerState.store.helper"; -import { ARIA_LABELS, CONST } from 'common/constants' -import { serialiseParcellationRegion } from "common/util" -import { actionAddToRegionsSelectionWithIds } from "src/services/state/viewerState/actions"; - -const filterRegionBasedOnText = searchTerm => region => `${region.name.toLowerCase()}${region.status? ' (' + region.status + ')' : null}`.includes(searchTerm.toLowerCase()) - || (region.relatedAreas && region.relatedAreas.some(relatedArea => relatedArea.name && relatedArea.name.toLowerCase().includes(searchTerm.toLowerCase()))) - -const compareFn = (it, item) => it.name === item.name && it.status === item.status - -@Component({ - selector: 'region-text-search-autocomplete', - templateUrl: './regionSearch.template.html', - styleUrls: [ - './regionSearch.style.css', - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) - -export class RegionTextSearchAutocomplete { - - public renderInputText(regionsSelected: any[]){ - if (!regionsSelected) return '' - if (regionsSelected.length === 0) return '' - if (regionsSelected.length === 1) return regionsSelected[0].name || '' - return CONST.MULTI_REGION_SELECTION - } - - public manualRenderList$: Subject<any> = new Subject() - - public compareFn = compareFn - - public CLEAR_SELECTED_REGION = ARIA_LABELS.CLEAR_SELECTED_REGION - - @Input() public ariaLabel: string = ARIA_LABELS.TEXT_INPUT_SEARCH_REGION - @Input() public showBadge: boolean = false - @Input() public showAutoComplete: boolean = true - - @ViewChild('regionHierarchyDialog', {read: TemplateRef}) public regionHierarchyDialogTemplate: TemplateRef<any> - - public useMobileUI$: Observable<boolean> - - public selectedRegionLabelIndexSet: Set<string> = new Set() - - constructor( - private store$: Store<any>, - private dialog: MatDialog, - private pureConstantService: PureContantService, - private log: LoggingService - ) { - - this.useMobileUI$ = this.pureConstantService.useTouchUI$ - - const viewerState$ = this.store$.pipe( - select('viewerState'), - shareReplay(1), - ) - - this.regionsWithLabelIndex$ = viewerState$.pipe( - select('parcellationSelected'), - distinctUntilChanged(), - filter(p => !!p && p.regions), - map(parcellationSelected => { - try { - const returnArray = [] - const ngIdMap = getMultiNgIdsRegionsLabelIndexMap(parcellationSelected, { ngId: 'root', relatedAreas: [], fullId: null }) - for (const [ngId, labelIndexMap] of ngIdMap) { - for (const [labelIndex, region] of labelIndexMap) { - returnArray.push({ - ...region, - ngId, - labelIndex, - labelIndexId: serialiseParcellationRegion({ ngId, labelIndex }), - }) - } - } - return returnArray - } catch (e) { - this.log.warn(`getMultiNgIdsRegionsLabelIndexMap error`, e) - return [] - } - }), - shareReplay(1), - ) - - this.regionsSelected$ = viewerState$.pipe( - select('regionsSelected'), - distinctUntilChanged(), - tap(regions => { - const arrLabelIndexId = regions.map(({ ngId, labelIndex }) => serialiseParcellationRegion({ ngId, labelIndex })) - this.selectedRegionLabelIndexSet = new Set(arrLabelIndexId) - }), - startWith([]), - shareReplay(1), - ) - - this.autocompleteList$ = combineLatest( - merge( - this.manualRenderList$.pipe( - withLatestFrom(this.regionsSelected$), - map(([_, selectedRegions]) => this.renderInputText(selectedRegions)), - startWith('') - ), - this.formControl.valueChanges.pipe( - startWith(''), - distinctUntilChanged(), - debounceTime(200), - ) - ), - this.regionsWithLabelIndex$.pipe( - startWith([]), - ), - ).pipe( - map(([searchTerm, regionsWithLabelIndex]) => regionsWithLabelIndex.filter(filterRegionBasedOnText(searchTerm))), - map(arr => arr.slice(0, 5)), - ) - - this.parcellationSelected$ = viewerState$.pipe( - select('parcellationSelected'), - distinctUntilChanged(), - shareReplay(1), - ) - } - - public toggleRegionWithId(id: string, removeFlag= false) { - if (removeFlag) { - this.store$.dispatch({ - type: VIEWER_STATE_ACTION_TYPES.DESELECT_REGIONS_WITH_ID, - deselecRegionIds: [id], - }) - } else { - this.store$.dispatch( - actionAddToRegionsSelectionWithIds({ - selectRegionIds : [id], - }) - ) - } - } - - public navigateTo(position) { - this.store$.dispatch({ - type: CHANGE_NAVIGATION, - navigation: { - position, - animation: {}, - }, - }) - } - - public optionSelected(_ev?: MatAutocompleteSelectedEvent) { - this.store$.dispatch( - viewerStateSetSelectedRegionsWithIds({ - selectRegionIds: _ev ? [ _ev.option.value ] : [] - }) - ) - } - - private regionsWithLabelIndex$: Observable<any[]> - public autocompleteList$: Observable<any[]> - public formControl = new FormControl() - - public filterNullFn(item: any){ - return !!item - } - public regionsSelected$: Observable<any> - public parcellationSelected$: Observable<any> - - @Output() - public focusedStateChanged: EventEmitter<boolean> = new EventEmitter() - - private _focused: boolean = false - set focused(val: boolean) { - this._focused = val - this.focusedStateChanged.emit(val) - } - get focused() { - return this._focused - } - - public deselectAllRegions(_event: MouseEvent) { - this.store$.dispatch({ - type: SELECT_REGIONS, - selectRegions: [], - }) - } - - // TODO handle mobile - public handleRegionClick({ mode = null, region = null } = {}) { - if (mode === 'single') { - this.store$.dispatch( - viewerStateToggleRegionSelect({ - payload: { region } - }) - ) - } - if (mode === 'double') { - this.store$.dispatch( - viewerStateNavigateToRegion({ - payload: { region } - }) - ) - } - } - - public showHierarchy(_event: MouseEvent) { - // mat-card-content has a max height of 65vh - const dialog = this.dialog.open(this.regionHierarchyDialogTemplate, { - height: '65vh', - panelClass: [ - 'col-10', - 'col-sm-10', - 'col-md-8', - 'col-lg-8', - 'col-xl-6', - ], - }) - - /** - * keep sleight of hand shown while modal is shown - * - */ - this.focused = true - - /** - * take 1 to avoid memory leak - */ - dialog.afterClosed().pipe( - take(1), - ).subscribe(() => this.focused = false) - } - - public deselectAndSelectRegion(region: any) { - this.store$.dispatch( - viewerStateSetSelectedRegions({ - selectRegions: [ region ] - }) - ) - } -} diff --git a/src/atlasComponents/parcellation/regionSearch/regionSearch.style.css b/src/atlasComponents/parcellation/regionSearch/regionSearch.style.css deleted file mode 100644 index 697cf662c0ed5ca19e9ccce62972112a79905000..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellation/regionSearch/regionSearch.style.css +++ /dev/null @@ -1,9 +0,0 @@ -region-hierarchy -{ - height: 100%; -} - -.regionAutocompleteOption -{ - height:38px; -} diff --git a/src/atlasComponents/parcellation/regionSearch/regionSearch.template.html b/src/atlasComponents/parcellation/regionSearch/regionSearch.template.html deleted file mode 100644 index 965cc3c65d508042857b48f489afaf00ee6e2364..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellation/regionSearch/regionSearch.template.html +++ /dev/null @@ -1,84 +0,0 @@ -<div class="w-100 d-inline-flex flex-row align-items-center"> - - <form *ngIf="showAutoComplete" class="d-flex flex-row flex-nowrap flex-grow-1 flex-shrink-1"> - <mat-form-field class="w-0 flex-grow-1 flex-shrink-1" - floatLabel="never"> - <input - placeholder="Search for regions" - [value]="renderInputText(regionsSelected$ | async)" - #trigger="matAutocompleteTrigger" - type="text" - (focus)="manualRenderList$.next()" - matInput - [attr.aria-label]="ariaLabel" - [formControl]="formControl" - [matAutocomplete]="auto"> - - <!-- close region selection --> - <button *ngIf="(regionsSelected$ | async)?.length > 0" - mat-icon-button - [attr.aria-label]="CLEAR_SELECTED_REGION" - (click)="optionSelected()" - matSuffix> - <i class="fas fa-times"></i> - </button> - - </mat-form-field> - <mat-autocomplete - (opened)="focused = true" - (closed)="focused = false" - panelWidth="auto" - (optionSelected)="optionSelected($event)" - autoActiveFirstOption - #auto="matAutocomplete"> - <mat-option - class="regionAutocompleteOption" - *ngFor="let region of autocompleteList$ | async" - [value]="region.labelIndexId"> - - <simple-region - [region]="region" - [isSelected]="regionsSelected$ | async | includes : region : compareFn"> - - </simple-region> - </mat-option> - </mat-autocomplete> - </form> - <!-- region hierarchy --> - <button - matBadgeColor="accent" - [matBadge]="showBadge && (regionsSelected$ | async).length ? (regionsSelected$ | async).length : null" - class="flex-grow-0 flex-shrink-0" - (click)="showHierarchy($event)" - mat-icon-button - color="primary"> - <i class="fas fa-sitemap"></i> - </button> -</div> - -<ng-template #regionHierarchyDialog> - <div class="h-100 d-flex flex-column"> - <mat-dialog-content class="flex-grow-1 flex-shrink-1"> - <ng-container *ngTemplateOutlet="regionHierarchy"> - </ng-container> - </mat-dialog-content> - - <mat-dialog-actions class="justify-content-center"> - <button mat-dialog-close mat-flat-button> - close - </button> - </mat-dialog-actions> - </div> -</ng-template> - -<ng-template #regionHierarchy> - <region-hierarchy - [useMobileUI]="useMobileUI$ | async" - [selectedRegions]="regionsSelected$ | async | filterArray : filterNullFn" - (singleClickRegion)="handleRegionClick({ mode: 'single', region: $event })" - (doubleClickRegion)="handleRegionClick({ mode: 'double', region: $event })" - (clearAllRegions)="deselectAllRegions($event)" - [parcellationSelected]="parcellationSelected$ | async"> - - </region-hierarchy> -</ng-template> diff --git a/src/atlasComponents/parcellationRegion/index.ts b/src/atlasComponents/parcellationRegion/index.ts deleted file mode 100644 index cced6b8edf56fd9c723124704267e3f5fd11e722..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { - ParcellationRegionModule, -} from "./module" - -export { RegionDirective } from "./region.directive"; -export { RegionMenuComponent } from "./regionMenu/regionMenu.component"; -export { SimpleRegionComponent } from "./regionSimple/regionSimple.component"; -export { RenderViewOriginDatasetLabelPipe } from "./region.base"; \ No newline at end of file diff --git a/src/atlasComponents/parcellationRegion/module.ts b/src/atlasComponents/parcellationRegion/module.ts deleted file mode 100644 index 03aceeb2d6353c60a3bdb08a634fbbd87a4199d8..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/module.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { ComponentsModule } from "src/components"; -import { AngularMaterialModule } from "src/sharedModules"; -import { UtilModule } from "src/util"; -import { RenderViewOriginDatasetLabelPipe } from "./region.base"; -import { RegionDirective } from "./region.directive"; -import { RegionMenuComponent } from "./regionMenu/regionMenu.component"; -import { SimpleRegionComponent } from "./regionSimple/regionSimple.component"; -import { BSFeatureModule } from "../regionalFeatures/bsFeatures"; -import { RegionAccordionTooltipTextPipe } from "./regionAccordionTooltipText.pipe"; -import { AtlasCmptConnModule } from "../connectivity"; -import { HttpClientModule } from "@angular/common/http"; -import { RegionInOtherTmplPipe } from "./regionInOtherTmpl.pipe"; -import { SiibraExplorerTemplateModule } from "../template"; -import { KgDatasetModule } from "../regionalFeatures/bsFeatures/kgDataset"; - -@NgModule({ - imports: [ - CommonModule, - UtilModule, - AngularMaterialModule, - ComponentsModule, - BSFeatureModule, - AtlasCmptConnModule, - HttpClientModule, - SiibraExplorerTemplateModule, - KgDatasetModule, - ], - declarations: [ - RegionMenuComponent, - SimpleRegionComponent, - - RegionDirective, - RenderViewOriginDatasetLabelPipe, - RegionAccordionTooltipTextPipe, - RegionInOtherTmplPipe, - ], - exports: [ - RegionMenuComponent, - SimpleRegionComponent, - - RegionDirective, - RenderViewOriginDatasetLabelPipe, - ] -}) - -export class ParcellationRegionModule{} \ No newline at end of file diff --git a/src/atlasComponents/parcellationRegion/region.base.spec.ts b/src/atlasComponents/parcellationRegion/region.base.spec.ts deleted file mode 100644 index 3e4085c70e1df483bd89f47e31dda4c3d167f1da..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/region.base.spec.ts +++ /dev/null @@ -1,335 +0,0 @@ -import { TestBed } from '@angular/core/testing' -import { MockStore, provideMockStore } from '@ngrx/store/testing' -import { viewerStateSelectTemplateWithId } from 'src/services/state/viewerState/actions' -import { RegionBase, getRegionParentParcRefSpace } from './region.base' -import { TSiibraExRegion } from './type' - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const util = require('common/util') - -/** - * regions - */ - -const mr0 = { - labelIndex: 1, - name: 'mr0', - availableIn: [{id: 'fzj/mock/rs/v0.0.0/aaa-bbb'}, {id: 'fzj/mock/rs/v0.0.0/bbb-bbb'}, {id: 'fzj/mock/rs/v0.0.0/ccc-bbb'}], - id: { - kg: { - kgSchema: 'fzj/mock/pr', - kgId: 'aaa-bbb' - } - } -} - -enum EnumParcRegVersion{ - V1_18 = 'V1_18', - V2_4 = "V2_4" -} - -describe('> region.base.ts', () => { - describe('> RegionBase', () => { - let regionBase: RegionBase - let mockStore: MockStore - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - provideMockStore() - ] - }) - mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(getRegionParentParcRefSpace, { template: null, parcellation: null }) - }) - describe('> position', () => { - beforeEach(() => { - regionBase = new RegionBase(mockStore) - }) - it('> does not populate if position property is absent', () => { - regionBase.region = { - ...mr0 - } as any - expect(regionBase.position).toBeFalsy() - }) - - describe('> does not populate if position property is malformed', () => { - it('> if region is falsy', () => { - regionBase.region = null - expect(regionBase.position).toBeFalsy() - }) - it('> if props is falsy', () => { - regionBase.region = { - ...mr0, - props: null - } as any - expect(regionBase.position).toBeFalsy() - }) - it('> if props.components is falsy', () => { - regionBase.region = { - ...mr0, - props: { - components: null - } - } as any - expect(regionBase.position).toBeFalsy() - }) - it('> if props.components[0] is falsy', () => { - regionBase.region = { - ...mr0, - props: { - components: [] - } - } as any - expect(regionBase.position).toBeFalsy() - }) - - it('> if props.components[0].centroid is falsy', () => { - - regionBase.region = { - ...mr0, - props: { - components: [{ - centroid: null - }] - } - } as any - expect(regionBase.position).toBeFalsy() - }) - }) - - it('> populates if position property is array with length 3 and non NaN element', () => { - regionBase.region = { - ...mr0, - props: { - components: [{ - centroid: [1, 2, 3] - }] - }, - } as any - expect(regionBase.position).toBeTruthy() - }) - }) - - describe('> rgb', () => { - let strToRgbSpy: jasmine.Spy - let mockStore: MockStore - beforeEach(() => { - strToRgbSpy = spyOn(util, 'strToRgb') - mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(getRegionParentParcRefSpace, { template: null, parcellation: null }) - }) - - afterEach(() => { - strToRgbSpy.calls.reset() - }) - - it('> will take region.rgb if exists', () => { - const regionBase = new RegionBase(mockStore) - regionBase.region = { - rgb: [100, 120, 140] - } as any - expect( - regionBase.rgbString - ).toEqual(`rgb(100,120,140)`) - }) - - it('> if rgb not provided, and labelIndex > 65500, set to white', () => { - - const regionBase = new RegionBase(mockStore) - regionBase.region = { - labelIndex: 65535 - } as any - expect( - regionBase.rgbString - ).toEqual(`rgb(255,255,255)`) - }) - - describe('> if rgb not provided, labelIndex < 65500', () => { - - describe('> arguments for strToRgb', () => { - it('> if ngId is defined, use ngId', () => { - - const regionBase = new RegionBase(mockStore) - regionBase.region = { - ngId: 'foo', - name: 'bar', - labelIndex: 152 - } as any - expect(strToRgbSpy).toHaveBeenCalledWith(`foo152`) - }) - it('> if ngId is not defined, use name', () => { - - const regionBase = new RegionBase(mockStore) - regionBase.region = { - name: 'bar', - labelIndex: 152 - } as any - expect(strToRgbSpy).toHaveBeenCalledWith(`bar152`) - }) - }) - - it('> calls strToRgb, and use return value for rgb', () => { - const getRandomNum = () => Math.floor(255*Math.random()) - const arr = [ - getRandomNum(), - getRandomNum(), - getRandomNum() - ] - strToRgbSpy.and.returnValue(arr) - const regionBase = new RegionBase(mockStore) - regionBase.region = { - foo: 'bar' - } as any - expect( - regionBase.rgbString - ).toEqual(`rgb(${arr.join(',')})`) - }) - - it('> if strToRgb returns falsy, uses fallback', () => { - - strToRgbSpy.and.returnValue(null) - const regionBase = new RegionBase(mockStore) - regionBase.region = { - foo: 'bar' - } as any - expect( - regionBase.rgbString - ).toEqual(`rgb(255,200,200)`) - }) - }) - }) - describe('> changeView', () => { - const fakeTmpl = { - '@id': 'faketmplid', - name: 'fakeTmpl' - } - const fakeParc = { - '@id': 'fakeparcid', - name: 'fakeParc' - } - beforeEach(() => { - regionBase = new RegionBase(mockStore) - }) - - describe('> [tmp] sameRegion to use transform backend', () => { - let dispatchSpy: jasmine.Spy - - beforeEach(() => { - dispatchSpy = spyOn(mockStore, 'dispatch') - }) - afterEach(() => { - dispatchSpy.calls.reset() - }) - - it('> calls viewerStateSelectTemplateWithId', () => { - - const partialRegion = { - context: { - parcellation: fakeParc, - atlas: { - "@id": '', - name: '', - parcellations: [], - templateSpaces: [fakeTmpl] - }, - template: null - } - } as Partial<TSiibraExRegion> - regionBase.region = partialRegion as any - regionBase.changeView(fakeTmpl) - - expect(dispatchSpy).toHaveBeenCalledWith( - viewerStateSelectTemplateWithId({ - payload: { - '@id': fakeTmpl['@id'] - }, - config: { - selectParcellation: { - "@id": fakeParc['@id'] - } - } - }) - ) - }) - }) - - /** - * currently, without position metadata, the navigation is broken - * fix changeView to fetch position metadata. If cannot, fallback to spatial backend - */ - - // describe('> if sameRegion has position attribute', () => { - // let dispatchSpy: jasmine.Spy - - // beforeEach(() => { - // dispatchSpy = spyOn(mockStore, 'dispatch') - // }) - // afterEach(() => { - // dispatchSpy.calls.reset() - // }) - // it('> malformed position is not an array > do not pass position', () => { - - // regionBase.changeView({ - // template: fakeTmpl, - // parcellation: fakeParc, - // region: { - // position: 'hello wolrd' - // } - // }) - - // expect(dispatchSpy).toHaveBeenCalledWith( - // viewerStateNewViewer({ - // selectTemplate: fakeTmpl, - // selectParcellation: fakeParc, - // navigation: {} - // }) - // ) - // }) - - // it('> malformed position is an array of incorrect size > do not pass position', () => { - - // regionBase.changeView({ - // template: fakeTmpl, - // parcellation: fakeParc, - // region: { - // position: [] - // } - // }) - - // expect(dispatchSpy).toHaveBeenCalledWith( - // viewerStateSelectTemplateWithId({ - // payload: { - // '@id': fakeTmpl['@id'] - // }, - // config: { - // selectParcellation: { - // "@id": fakeParc['@id'] - // } - // } - // }) - // ) - // }) - - // it('> correct position > pass position', () => { - // regionBase.changeView({ - // template: fakeTmpl, - // parcellation: fakeParc, - // region: { - // position: [1,2,3] - // } - // }) - - // expect(dispatchSpy).toHaveBeenCalledWith( - // viewerStateNewViewer({ - // selectTemplate: fakeTmpl, - // selectParcellation: fakeParc, - // navigation: { - // position: [1,2,3] - // } - // }) - // ) - // }) - // }) - }) - }) -}) diff --git a/src/atlasComponents/parcellationRegion/region.base.ts b/src/atlasComponents/parcellationRegion/region.base.ts deleted file mode 100644 index 5f4b7d327e4e1432abb128187ec7c738b6a1bd29..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/region.base.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { Directive, EventEmitter, Input, Output, Pipe, PipeTransform } from "@angular/core"; -import { select, Store, createSelector } from "@ngrx/store"; -import { uiStateOpenSidePanel, uiStateExpandSidePanel, uiActionShowSidePanelConnectivity } from 'src/services/state/uiState.store.helper' -import { map, tap } from "rxjs/operators"; -import { Observable, BehaviorSubject, combineLatest } from "rxjs"; -import { rgbToHsl } from 'common/util' -import { viewerStateSetConnectivityRegion, viewerStateNavigateToRegion, viewerStateToggleRegionSelect, viewerStateSelectTemplateWithId } from "src/services/state/viewerState.store.helper"; -import { viewerStateGetSelectedAtlas, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors"; -import { strToRgb, verifyPositionArg } from 'common/util' -import { getPosFromRegion } from "src/util/siibraApiConstants/fn"; -import { TRegionDetail } from "src/util/siibraApiConstants/types"; -import { IHasId } from "src/util/interfaces"; -import { TSiibraExTemplate } from "./type"; - -@Directive() -export class RegionBase { - - public rgbString: string - public rgbDarkmode: boolean - - private _region: TRegionDetail & { - context?: { - atlas: IHasId - template: IHasId - parcellation: IHasId - } - ngId?: string - } - - private _position: [number, number, number] - set position(val){ - if (verifyPositionArg(val)) { - this._position = val - } else { - this._position = null - } - } - - get position(){ - return this._position - } - - @Input() - set region(val) { - this._region = val - this.region$.next(this._region) - this.hasContext$.next(!!this._region?.context) - - this.position = null - // bug the centroid returned is currently nonsense - // this.position = val?.props?.centroid_mm - if (!this._region) return - const pos = getPosFromRegion(val) - if (pos) { - this.position = pos - } - - const rgb = this._region.rgb - || (this._region.labelIndex > 65500 && [255, 255, 255]) - || strToRgb(`${this._region.ngId || this._region.name}${this._region.labelIndex}`) - || [255, 200, 200] - - this.rgbString = `rgb(${rgb.join(',')})` - const [_h, _s, l] = rgbToHsl(...rgb) - this.rgbDarkmode = l < 0.4 - } - - get region(){ - return this._region - } - - get originDatainfos(){ - if (!this._region) return [] - return (this._region._dataset_specs || []).filter(spec => spec['@type'] === 'minds/core/dataset/v1.0.0') - } - - public hasContext$: BehaviorSubject<boolean> = new BehaviorSubject(false) - public region$: BehaviorSubject<any> = new BehaviorSubject(null) - - @Input() - public isSelected: boolean = false - - @Input() public hasConnectivity: boolean - - @Output() public closeRegionMenu: EventEmitter<boolean> = new EventEmitter() - - public regionOriginDatasetLabels$: Observable<{ name: string }[]> - public selectedAtlas$: Observable<any> = this.store$.pipe( - select(viewerStateGetSelectedAtlas) - ) - - - constructor( - private store$: Store<any>, - ) { - - this.regionOriginDatasetLabels$ = combineLatest([ - this.store$, - this.region$ - ]).pipe( - map(([state, region]) => getRegionParentParcRefSpace(state, { region })), - map(({ template }) => (template && template.originalDatasetFormats) || []) - ) - } - - public selectedTemplate$ = this.store$.pipe( - select(viewerStateSelectedTemplatePureSelector), - ) - - public navigateToRegion() { - this.closeRegionMenu.emit() - const { region } = this - this.store$.dispatch( - viewerStateNavigateToRegion({ payload: { region } }) - ) - } - - public toggleRegionSelected() { - this.closeRegionMenu.emit() - const { region } = this - this.store$.dispatch( - viewerStateToggleRegionSelect({ payload: { region } }) - ) - } - - public showConnectivity(regionName) { - this.closeRegionMenu.emit() - // ToDo trigger side panel opening with effect - this.store$.dispatch(uiStateOpenSidePanel()) - this.store$.dispatch(uiStateExpandSidePanel()) - this.store$.dispatch(uiActionShowSidePanelConnectivity()) - - this.store$.dispatch( - viewerStateSetConnectivityRegion({ connectivityRegion: regionName }) - ) - } - - changeView(template: TSiibraExTemplate) { - - this.closeRegionMenu.emit() - - const { - parcellation - } = (this.region?.context || {}) - - /** - * TODO use createAction in future - * for now, not importing const because it breaks tests - */ - this.store$.dispatch( - viewerStateSelectTemplateWithId({ - payload: { - '@id': template['@id'] || template['fullId'] - }, - config: { - selectParcellation: { - '@id': parcellation['@id'] || parcellation['fullId'] - } - } - }) - ) - } -} - -export const getRegionParentParcRefSpace = createSelector( - (state: any) => state.viewerState, - viewerStateGetSelectedAtlas, - (viewerState, selectedAtlas, prop) => { - const { region: regionOfInterest } = prop - /** - * if region is undefined, return null - */ - if (!regionOfInterest || !viewerState.parcellationSelected || !viewerState.templateSelected) { - return { - template: null, - parcellation: null - } - } - /** - * first check if the region can be found in the currently selected parcellation - */ - const checkRegions = regions => { - for (const region of regions) { - - /** - * check ROI to iterating regions - */ - if (region.name === regionOfInterest.name) return true - - if (region && region.children && Array.isArray(region.children)) { - const flag = checkRegions(region.children) - if (flag) return true - } - } - return false - } - const regionInParcSelected = checkRegions(viewerState.parcellationSelected.regions) - - if (regionInParcSelected) { - const p = selectedAtlas.parcellations.find(p => p['@id'] === viewerState.parcellationSelected['@id']) - if (p) { - const refSpace = p.availableIn.find(refSpace => refSpace['@id'] === viewerState.templateSelected['@id']) - return { - template: refSpace, - parcellation: p - } - } - } - - return { - template: null, - parcellation: null - } - } -) - -@Pipe({ - name: 'renderViewOriginDatasetlabel' -}) - -export class RenderViewOriginDatasetLabelPipe implements PipeTransform{ - public transform(originDatasetlabels: { name: string }[], index: string|number){ - if (!!originDatasetlabels && !!originDatasetlabels[index] && !!originDatasetlabels[index].name) { - return `${originDatasetlabels[index]['name']}` - } - return `origin dataset` - } -} diff --git a/src/atlasComponents/parcellationRegion/region.directive.ts b/src/atlasComponents/parcellationRegion/region.directive.ts deleted file mode 100644 index fb66743eb6ec06a570b9c91223476d44695d1748..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/region.directive.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Directive } from "@angular/core"; -import { RegionBase } from "./region.base"; -import { Store } from "@ngrx/store"; - -@Directive({ - selector: '[iav-region]', - exportAs: 'iavRegion' -}) - -export class RegionDirective extends RegionBase{ - constructor(store: Store<any>){ - super(store) - } -} diff --git a/src/atlasComponents/parcellationRegion/regionAccordionTooltipText.pipe.ts b/src/atlasComponents/parcellationRegion/regionAccordionTooltipText.pipe.ts deleted file mode 100644 index c2a92b83de75ee5660c0d59a60e19f561581bf31..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/regionAccordionTooltipText.pipe.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core" - -@Pipe({ - name: 'regionAccordionTooltipTextPipe', - pure: true -}) - -export class RegionAccordionTooltipTextPipe implements PipeTransform{ - - public transform(length: number, type: string): string{ - switch (type) { - case 'regionInOtherTmpl': return `Region available in ${length} other reference space${length > 1 ? 's' : ''}` - case 'regionalFeatures': return `${length} regional feature${length > 1 ? 's' : ''} found` - case 'connectivity': return `${length} connections found` - default: return `${length} items found` - } - } -} diff --git a/src/atlasComponents/parcellationRegion/regionInOtherTmpl.pipe.ts b/src/atlasComponents/parcellationRegion/regionInOtherTmpl.pipe.ts deleted file mode 100644 index 3a0e81dc8ee756aa29b6112e1ebab77987f33ed5..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/regionInOtherTmpl.pipe.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { TSiibraExRegion } from "./type"; - -@Pipe({ - name: 'regionInOtherTmpl', - pure: true -}) - -export class RegionInOtherTmplPipe implements PipeTransform{ - public transform(region: TSiibraExRegion){ - const { templateSpaces: allTmpl = [] } = region?.context?.atlas || {} - return allTmpl.filter(t => (region?.availableIn || []).find(availTmpl => availTmpl['id'] === t["@id"])) - } -} diff --git a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.spec.ts b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.spec.ts deleted file mode 100644 index 2c9d299636b1aab010b6af672210079c28611d87..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.spec.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { TestBed, async } from "@angular/core/testing" -import { RegionMenuComponent } from "./regionMenu.component" -import { AngularMaterialModule } from "src/sharedModules" -import { UtilModule } from "src/util/util.module" -import { CommonModule } from "@angular/common" -import { provideMockStore } from "@ngrx/store/testing" -import { Component, Directive, Input } from "@angular/core" -import { NoopAnimationsModule } from "@angular/platform-browser/animations" -import { ComponentsModule } from "src/components" -import { ParcellationRegionModule } from "../module" -import { BS_ENDPOINT } from "src/util/constants" - -const mt0 = { - name: 'mt0' -} - -const mt1 = { - name: 'mt1' -} - -const mr0 = { - name: 'mr0' -} - -const mr1 = { - name: 'mr1' -} - -const mp0 = { - name: 'mp0' -} - -const mp1 = { - name: 'mp1' -} - -const mrm0 = { - template: mt0, - parcellation: mp0, - region: mr0 -} - -const mrm1 = { - template: mt1, - parcellation: mp1, - region: mr1 -} - -const hemisphereMrms = [ { - ...mrm0, - hemisphere: 'left hemisphere' -}, { - ...mrm1, - hemisphere: 'left hemisphere' -} ] - -const nohemisphereHrms = [mrm0, mrm1] - -@Component({ - selector: 'kg-regional-features-list', - template: '' -}) - -class DummyKgRegionalFeatureList{} - -@Directive({ - selector: '[kg-regional-features-list-directive]', - exportAs: 'kgRegionalFeaturesListDirective' -}) - -class DummySingleDatasetDirective{ - @Input() - region: string - -} - -describe('> regionMenu.component.ts', () => { - describe('> RegionMenuComponent', () => { - beforeEach(async(() => { - - TestBed.configureTestingModule({ - imports: [ - UtilModule, - AngularMaterialModule, - CommonModule, - NoopAnimationsModule, - ComponentsModule, - ParcellationRegionModule, - ], - declarations: [ - /** - * Used by regionMenu.template.html to show region preview - */ - DummySingleDatasetDirective, - DummyKgRegionalFeatureList, - ], - providers: [ - provideMockStore({ initialState: {} }), - { - provide: BS_ENDPOINT, - useValue: 'http://example.dev/1_0' - } - ] - }).compileComponents() - - })) - - it('> init just fine', () => { - const fixture = TestBed.createComponent(RegionMenuComponent) - expect(fixture).toBeTruthy() - }) - }) -}) \ No newline at end of file diff --git a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.ts b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.ts deleted file mode 100644 index f6456eba6b90135ae35ea96b110834093b46a4f8..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Component, OnDestroy } from "@angular/core"; -import { Store } from "@ngrx/store"; -import { merge, Observable, Subject, Subscription } from "rxjs"; -import { RegionBase } from '../region.base' -import { CONST, ARIA_LABELS } from 'common/constants' -import { ComponentStore } from "src/viewerModule/componentStore"; -import { distinctUntilChanged, mapTo } from "rxjs/operators"; - -@Component({ - selector: 'region-menu', - templateUrl: './regionMenu.template.html', - styleUrls: ['./regionMenu.style.css'], - providers: [ ComponentStore ] -}) -export class RegionMenuComponent extends RegionBase implements OnDestroy { - - public CONST = CONST - public ARIA_LABELS = ARIA_LABELS - private subscriptions: Subscription[] = [] - - public activePanelTitles$: Observable<string[]> - private activePanelTitles: string[] = [] - - public intentToChgTmpl$ = new Subject() - public lockOtherTmpl$ = merge( - this.selectedTemplate$.pipe( - mapTo(false) - ), - this.intentToChgTmpl$.pipe( - mapTo(true) - ) - ).pipe( - distinctUntilChanged() - ) - - constructor( - store$: Store<any>, - private viewerCmpLocalUiStore: ComponentStore<{ activePanelsTitle: string[] }>, - ) { - super(store$) - this.viewerCmpLocalUiStore.setState({ - activePanelsTitle: [] - }) - - this.activePanelTitles$ = this.viewerCmpLocalUiStore.select( - state => state.activePanelsTitle - ) as Observable<string[]> - - this.subscriptions.push( - this.activePanelTitles$.subscribe( - (activePanelTitles: string[]) => this.activePanelTitles = activePanelTitles - ) - ) - } - - ngOnDestroy(): void { - this.subscriptions.forEach(s => s.unsubscribe()) - } - - handleExpansionPanelClosedEv(title: string){ - this.viewerCmpLocalUiStore.setState({ - activePanelsTitle: this.activePanelTitles.filter(n => n !== title) - }) - } - handleExpansionPanelAfterExpandEv(title: string){ - if (this.activePanelTitles.includes(title)) return - this.viewerCmpLocalUiStore.setState({ - activePanelsTitle: [ - ...this.activePanelTitles, - title - ] - }) - } - - public busyFlag = false - private busyMap = new Map<string, boolean>() - handleBusySignal(namespace: string, flag: boolean) { - this.busyMap.set(namespace, flag) - for (const [_key, val] of this.busyMap.entries()) { - if (val) { - this.busyFlag = true - return - } - } - this.busyFlag = false - } -} diff --git a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.style.css b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.style.css deleted file mode 100644 index 386e688cd8e1adc6489f109fbadf71148c4d5ccc..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.style.css +++ /dev/null @@ -1,54 +0,0 @@ -mat-icon -{ - transform: scale(0.75); -} - -.action-list -{ - margin-left: -16px; - margin-right: -16px; -} - -.action-list mat-icon -{ - padding-left: 0px; -} - -.region-name -{ - display: inherit; - font-size: 95%; - line-height: normal; -} - -:host-context([darktheme="true"]) .loading-overlay -{ - background-color: rgba(10, 10, 10, 0.8); -} - -.loading-overlay -{ - background-color: rgba(250, 250, 250, 0.8); -} - -.loading-overlay -{ - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - font-size: 200%; - - display: grid; - grid-template-columns: auto; - grid-template-rows: 1fr auto 1fr; - grid-template-columns: 1fr auto 1fr; - grid-template-areas: "." "vertical-center" "."; -} - -.loading-overlay > .spinner -{ - grid-column: 2; - grid-row: 2; -} diff --git a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html deleted file mode 100644 index 55539c2c1993146b799f90acc9555d8456c1c1ae..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html +++ /dev/null @@ -1,239 +0,0 @@ -<ng-template [ngIf]="region"> - -<mat-card class="mat-elevation-z4"> - <!-- rgbDarkmode must be checked for strict equality to true/false - as if rgb is undefined, rgbDarkmode will be null/undefined - which is falsy --> - <div class="sidenav-cover-header-container" - [ngClass]="{'darktheme': rgbDarkmode === true, 'lighttheme': rgbDarkmode === false}" - [style.backgroundColor]="rgbString"> - <mat-card-title> - <div class="position-relative region-name iv-custom-comp text"> - {{ region.name }} - </div> - </mat-card-title> - - <!-- subtitle on what it is --> - <mat-card-subtitle class="d-inline-flex align-items-center flex-wrap"> - <mat-icon fontSet="fas" fontIcon="fa-brain"></mat-icon> - <span> - Brain region - </span> - - <!-- origin datas format --> - - <mat-divider vertical="true" class="ml-2 h-2rem"></mat-divider> - - <!-- position --> - <button mat-icon-button *ngIf="position" - (click)="navigateToRegion()" - [matTooltip]="ARIA_LABELS.GO_TO_REGION_CENTROID + ': ' + (position | nmToMm | addUnitAndJoin : 'mm')"> - <mat-icon fontSet="fas" fontIcon="fa-map-marked-alt"> - </mat-icon> - </button> - - <!-- explore doi --> - <ng-template let-infos [ngIf]="originDatainfos"> - <ng-container *ngFor="let info of infos"> - <a *ngFor="let url of info.urls" - [href]="url.doi | doiParserPipe" - [matTooltip]="ARIA_LABELS.EXPLORE_DATASET_IN_KG" - target="_blank" - mat-icon-button> - <i class="fas fa-external-link-alt"></i> - </a> - </ng-container> - </ng-template> - - </mat-card-subtitle> - - </div> -</mat-card> - -<mat-accordion class="d-block mt-2"> - - <!-- description --> - <ng-template [ngIf]="(originDatainfos || []).length > 0"> - <ng-container *ngFor="let originData of originDatainfos"> - <ng-template #descTmpl> - <markdown-dom [markdown]="originData.description" - class="text-muted text-sm"> - </markdown-dom> - </ng-template> - - <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { - title: 'Description', - iconClass: 'fas fa-info', - iavNgIf: true, - content: descTmpl - }"> - - </ng-container> - </ng-container> - </ng-template> - - - <!-- Explore in other template --> - - <!-- Disabling explore in other templates for now --> - <!-- rework before reenable - - identify the open todos (bigbrain hemisphere split, navigation to center coordinates in surface viewer, …) until the functionality is working - - put it in the development board --> - <ng-template [ngIf]="false"> - - <ng-template [ngIf]="region$ | async | regionInOtherTmpl" let-otherTmpls> - <ng-template #exploreInOtherTmpl> - <mat-grid-list cols="3" - rowHeight="2:3" - gutterSize="16" - class="position-relative"> - <mat-grid-tile *ngFor="let otherTmpl of otherTmpls"> - - <div [hidden] - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-name]="otherTmpl.originDatainfos[0]?.name" - [iav-dataset-show-dataset-dialog-description]="otherTmpl.originDatainfos[0]?.description" - [iav-dataset-show-dataset-dialog-urls]="otherTmpl.originDatainfos[0]?.urls" - [iav-dataset-show-dataset-dialog-ignore-overwrite]="true" - #kgInfo="iavDatasetShowDatasetDialog"> - </div> - <tile-cmp [tile-image-src]="otherTmpl | getTemplatePreviewUrl" - class="cursor-pointer pe-all" - tile-image-alt="Preview of this tile" - [tile-text]="otherTmpl.displayName || otherTmpl.name" - [tile-show-info]="otherTmpl.originDatainfos?.length > 0" - [tile-image-darktheme]="otherTmpl | templateIsDarkTheme" - [tile-selected]="(selectedTemplate$ | async | getProperty : '@id') === otherTmpl['@id']" - (tile-on-click)="(tileCmp.selected || changeView(otherTmpl)); (tileCmp.selected || intentToChgTmpl$.next(true))" - (tile-on-info-click)="kgInfo && kgInfo.onClick()" - #tileCmp="tileCmp"> - </tile-cmp> - </mat-grid-tile> - </mat-grid-list> - - <div *ngIf="lockOtherTmpl$ | async" class="loading-overlay"> - <spinner-cmp class="spinner"></spinner-cmp> - </div> - - </ng-template> - - <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { - title: 'Explore in other templates', - desc: otherTmpls.length, - iconClass: 'fas fa-brain', - iconTooltip: otherTmpls.length | regionAccordionTooltipTextPipe : 'regionInOtherTmpl', - iavNgIf: otherTmpls.length > 0, - content: exploreInOtherTmpl - }"> - </ng-container> - </ng-template> - </ng-template> - - - <!-- kg regional features list --> - <ng-template #kgRegionalFeatureList> - <regional-feature-wrapper [region]="region$ | async"> - </regional-feature-wrapper> - </ng-template> - - <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { - title: CONST.REGIONAL_FEATURES, - iconClass: 'fas fa-database', - content: kgRegionalFeatureList, - desc: '', - iconTooltip: 'Regional Features', - iavNgIf: hasContext$ | async - }"> - </ng-container> - - <!-- Connectivity --> - - <ng-template #connectivityContentTmpl let-expansionPanel="expansionPanel"> - <mat-card-content class="flex-grow-1 flex-shrink-1 w-100"> - <connectivity-browser class="pe-all flex-shrink-1" - [region]="region" - (setOpenState)="expansionPanel.expanded = $event" - [accordionExpanded]="expansionPanel.expanded" - (connectivityNumberReceived)="hasConnectivityDirective.connectivityNumber = $event"> - </connectivity-browser> - </mat-card-content> - </ng-template> - - <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { - title: 'Connectivity', - desc: hasConnectivityDirective.connectivityNumber, - iconClass: 'fas fa-braille', - iconTooltip: hasConnectivityDirective.connectivityNumber | regionAccordionTooltipTextPipe : 'connectivity', - iavNgIf: hasConnectivityDirective.hasConnectivity, - content: connectivityContentTmpl - }"> - </ng-container> - - <div has-connectivity - [region]="[region]" - #hasConnectivityDirective="hasConnectivityDirective"> - </div> -</mat-accordion> - -<div *ngIf="busyFlag" class="mt-2 d-flex justify-content-center"> - <spinner-cmp></spinner-cmp> -</div> - -<!-- expansion tmpl --> -<ng-template #ngMatAccordionTmpl - let-title="title" - let-desc="desc" - let-iconClass="iconClass" - let-iconTooltip="iconTooltip" - let-iavNgIf="iavNgIf" - let-content="content"> - <mat-expansion-panel - [expanded]="activePanelTitles$ | async | includes : title" - [attr.data-opened]="expansionPanel.expanded" - [attr.data-mat-expansion-title]="title" - (closed)="handleExpansionPanelClosedEv(title)" - (afterExpand)="handleExpansionPanelAfterExpandEv(title)" - hideToggle - *ngIf="iavNgIf" - #expansionPanel="matExpansionPanel"> - - <mat-expansion-panel-header> - - <!-- title --> - <mat-panel-title> - {{ title }} - </mat-panel-title> - - <!-- desc + icon --> - <mat-panel-description class="d-flex align-items-center justify-content-end" - [matTooltip]="iconTooltip"> - <span class="mr-3">{{ desc }}</span> - <span class="accordion-icon d-inline-flex justify-content-center"> - <i [class]="iconClass"></i> - </span> - </mat-panel-description> - - </mat-expansion-panel-header> - - <!-- content --> - <ng-template matExpansionPanelContent> - <ng-container *ngTemplateOutlet="content; context: { - expansionPanel: expansionPanel - }"> - </ng-container> - </ng-template> - </mat-expansion-panel> -</ng-template> -</ng-template> - - -<ng-template [ngIf]="!region"> - <mat-card class="mat-elevation-z4"> - <h1 class="mat-h1 sidenav-cover-header-container"> - <spinner-cmp class="d-inline-block"></spinner-cmp> - <span class="text-muted"> - Loading region - </span> - </h1> - </mat-card> -</ng-template> \ No newline at end of file diff --git a/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.component.ts b/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.component.ts deleted file mode 100644 index 76859b9e5341053c37c14c3a57115cc1a6b5f190..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component } from '@angular/core' - -import { Store } from '@ngrx/store' -import { IavRootStoreInterface } from 'src/services/stateStore.service' -import { RegionBase } from '../region.base' - -@Component({ - selector: 'simple-region', - templateUrl: './regionSimple.template.html', - styleUrls: [ - './regionSimple.style.css', - ], -}) - -export class SimpleRegionComponent extends RegionBase { - constructor( - store$: Store<IavRootStoreInterface>, - ) { - super(store$) - } -} diff --git a/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.template.html b/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.template.html deleted file mode 100644 index b724ab6f27d2ece74413959934d913d7b48888f5..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/regionSimple/regionSimple.template.html +++ /dev/null @@ -1,26 +0,0 @@ -<div class="d-flex flex-row"> - - <small class="text-truncate flex-shrink-1 flex-grow-1"> - {{ region.name }} - </small> - - <div class="flex-grow-0 flex-shrink-0 d-flex flex-row"> - - <!-- if has position defined --> - <button *ngIf="position" - iav-stop="click" - (click)="navigateToRegion()" - mat-icon-button> - <i class="fas fa-map-marked-alt"></i> - </button> - - <!-- region selected --> - <button mat-icon-button - [color]="isSelected ? 'primary' : 'basic'"> - <i class="far" - [ngClass]="{'fa-check-square': isSelected, 'fa-square': !isSelected}"> - </i> - </button> - </div> - -</div> \ No newline at end of file diff --git a/src/atlasComponents/parcellationRegion/type.ts b/src/atlasComponents/parcellationRegion/type.ts deleted file mode 100644 index e5c40a75fc1c63e88c92e4e388c56603feef1625..0000000000000000000000000000000000000000 --- a/src/atlasComponents/parcellationRegion/type.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { IHasId } from "src/util/interfaces"; -import { TRegionSummary } from "src/util/siibraApiConstants/types"; - -type TAny = { - [key: string]: any -} - -export type TSiibraExTemplate = IHasId & TAny -export type TSiibraExParcelation = IHasId & TAny - -export type TSiibraExAtlas = { - name: string - '@id': string - parcellations: TSiibraExParcelation[] - templateSpaces: TSiibraExTemplate[] -} - -export type TSiibraExRegion = TRegionSummary & { - context: { - atlas: TSiibraExAtlas - template: TSiibraExTemplate - parcellation: TSiibraExParcelation - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/bsRegionInputBase.ts b/src/atlasComponents/regionalFeatures/bsFeatures/bsRegionInputBase.ts deleted file mode 100644 index 85d2e6d005bca99c7d003f148bc6a93fe7e2acb7..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/bsRegionInputBase.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { BehaviorSubject, throwError } from "rxjs"; -import { map, switchMap } from "rxjs/operators"; -import { TRegion, IBSSummaryResponse, IBSDetailResponse } from "./type"; -import { BsFeatureService, TFeatureCmpInput } from "./service"; -import { flattenReducer } from 'common/util' -import { Directive, Input } from "@angular/core"; - -@Directive() -export class BsRegionInputBase{ - - protected region$ = new BehaviorSubject<TRegion>(null) - private _region: TRegion - - @Input() - set region(val: TRegion) { - this._region = val - this.region$.next(val) - } - - get region() { - return this._region - } - - constructor( - private svc: BsFeatureService, - data?: TFeatureCmpInput - ){ - if (data) { - this.region = data.region - } - } - - protected featuresList$ = this.region$.pipe( - switchMap(region => this.svc.listFeatures(region)), - map(result => result.features.map(obj => Object.keys(obj).reduce(flattenReducer, []))) - ) - - protected getFeatureInstancesList<T extends keyof IBSSummaryResponse>(feature: T){ - if (!this._region) return throwError('#getFeatureInstancesList region needs to be defined') - return this.svc.getFeatures<T>(feature, this._region) - } - - protected getFeatureInstance<T extends keyof IBSDetailResponse>(feature: T, id: string) { - if (!this._region) return throwError('#getFeatureInstance region needs to be defined') - return this.svc.getFeature<T>(feature, this._region, id) - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/constants.ts b/src/atlasComponents/regionalFeatures/bsFeatures/constants.ts deleted file mode 100644 index 3746ac94431b1b414a26a5e5fb1d6aa3b46ab0a4..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { InjectionToken } from "@angular/core"; -import { Observable } from "rxjs"; -export { BS_ENDPOINT } from 'src/util/constants' - -export const BS_DARKTHEME = new InjectionToken<Observable<boolean>>('BS_DARKTHEME') -export const REGISTERED_FEATURE_INJECT_DATA = new InjectionToken('REGISTERED_FEATURE_INJECT_DATA') diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/genericInfoCmp/genericInfo.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/genericInfoCmp/genericInfo.component.ts deleted file mode 100644 index ca0d7c57c1c17a4fe011b05d05bd5d1cea13f6f5..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/genericInfoCmp/genericInfo.component.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { AfterViewInit, ChangeDetectorRef, Component, Inject, Input, OnChanges, OnDestroy, Optional, TemplateRef, ViewChild, ViewContainerRef, ViewRef } from "@angular/core"; -import { BsRegionInputBase } from "../../bsRegionInputBase"; -import { KG_REGIONAL_FEATURE_KEY, TBSDetail, UNDER_REVIEW } from "../../kgRegionalFeature/type"; -import { ARIA_LABELS, CONST } from 'common/constants' -import { TBSSummary } from "../../kgDataset"; -import { BsFeatureService } from "../../service"; -import { MAT_DIALOG_DATA } from "@angular/material/dialog"; -import { TDatainfosDetail } from "src/util/siibraApiConstants/types"; -import { TRegion } from "../../type"; - -/** - * this component is specifically used to render side panel ebrains dataset view - */ - -export type TInjectableData = TDatainfosDetail & { - dataType?: string - view?: ViewRef | TemplateRef<any> - region?: TRegion - summary?: TBSSummary - isGdprProtected?: boolean -} - -@Component({ - selector: 'generic-info-cmp', - templateUrl: './genericInfo.template.html', - styleUrls: [ - './genericInfo.style.css' - ] -}) - -export class GenericInfoCmp extends BsRegionInputBase implements OnChanges, AfterViewInit, OnDestroy { - - public ARIA_LABELS = ARIA_LABELS - public CONST = CONST - - @Input() - public summary: TBSSummary - - @Input() - public detail: TBSDetail - - public loadingFlag = false - public error = null - - public nameFallback = `[This dataset cannot be fetched right now]` - public isGdprProtected = false - - public descriptionFallback = `[This dataset cannot be fetched right now]` - public useClassicUi = false - public dataType = 'ebrains regional dataset' - - public description: string - public name: string - public urls: { - cite: string - doi: string - }[] - - public doiUrls: { - cite: string - doi: string - }[] - - template: TemplateRef<any> - viewref: ViewRef - - @ViewChild('insertViewTarget', { read: ViewContainerRef }) - insertedViewVCR: ViewContainerRef - - constructor( - svc: BsFeatureService, - private cdr: ChangeDetectorRef, - @Optional() @Inject(MAT_DIALOG_DATA) data: TInjectableData - ){ - super(svc) - if (data) { - const { dataType, description, name, urls, useClassicUi, view, region, summary, isGdprProtected } = data - this.description = description - this.name = name - this.urls = urls || [] - this.doiUrls = this.urls.filter(d => !!d.doi) - this.useClassicUi = useClassicUi - if (dataType) this.dataType = dataType - if (typeof isGdprProtected !== 'undefined') this.isGdprProtected = isGdprProtected - - if (!!view) { - if (view instanceof TemplateRef){ - this.template = view - } else { - this.viewref = view - } - } - - if (region && summary) { - this.region = region - this.summary = summary - this.ngOnChanges() - } - } - } - - ngOnDestroy(){ - this.insertedViewVCR.clear() - } - - ngAfterViewInit(){ - if (this.insertedViewVCR && this.viewref) { - this.insertedViewVCR.insert(this.viewref) - } - } - - ngOnChanges(){ - if (!this.region) return - if (!this.summary) return - if (!!this.detail) return - this.loadingFlag = true - this.getFeatureInstance(KG_REGIONAL_FEATURE_KEY, this.summary['@id']).subscribe( - detail => { - this.detail = detail - - this.name = this.detail.src_name - this.description = this.detail.__detail?.description - this.urls = this.detail.__detail.kgReference.map(url => { - return { cite: null, doi: url } - }) - - this.isGdprProtected = detail.__detail.embargoStatus && detail.__detail.embargoStatus.some(status => status["@id"] === UNDER_REVIEW["@id"]) - }, - err => { - this.error = err.toString() - }, - () => { - this.loadingFlag = false - this.cdr.markForCheck() - } - ) - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/genericInfoCmp/genericInfo.style.css b/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/genericInfoCmp/genericInfo.style.css deleted file mode 100644 index 3e722e3e9997fb043174c54f56a641035c3f1b6d..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/genericInfoCmp/genericInfo.style.css +++ /dev/null @@ -1,5 +0,0 @@ -.desc-container -{ - max-height: 35vh; - overflow: auto; -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/genericInfoCmp/genericInfo.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/genericInfoCmp/genericInfo.template.html deleted file mode 100644 index be13818c97aec683681de104eba5f82ea9d0e6d8..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/genericInfoCmp/genericInfo.template.html +++ /dev/null @@ -1,126 +0,0 @@ - -<!-- classic UI --> -<ng-template [ngIf]="useClassicUi" [ngIfElse]="modernUi"> - <mat-card-subtitle> - <ng-container *ngTemplateOutlet="nameTmpl"> - </ng-container> - </mat-card-subtitle> - - - <!-- desc --> - <small> - <ng-container *ngTemplateOutlet="descTmpl"></ng-container> - </small> - - <ng-container *ngTemplateOutlet="insertedView"> - </ng-container> - - <!-- footer --> - <mat-card-actions iav-media-query #iavMediaQuery="iavMediaQuery"> - <ng-container *ngTemplateOutlet="actionBtnsTmpl; context: { - $implicit: (iavMediaQuery.mediaBreakPoint$ | async) > 1 - }" > - </ng-container> - </mat-card-actions> - - <ng-template #actionBtnsTmpl let-useSmallIcon> - <!-- explore --> - <ng-container> - - <a *ngFor="let kgRef of (doiUrls || [])" - [href]="kgRef.doi | doiParserPipe" - class="color-inherit" - [matTooltip]="ARIA_LABELS.EXPLORE_DATASET_IN_KG" - target="_blank"> - <iav-dynamic-mat-button - [iav-dynamic-mat-button-style]="useSmallIcon ? 'mat-icon-button' : 'mat-raised-button'" - iav-dynamic-mat-button-color="primary"> - - <span *ngIf="!useSmallIcon"> - Explore - </span> - <i class="fas fa-external-link-alt"></i> - </iav-dynamic-mat-button> - </a> - </ng-container> - </ng-template> -</ng-template> - -<!-- modern UI --> -<ng-template #modernUi> - <!-- header --> - <mat-card class="mat-elevation-z4"> - - <div class="sidenav-cover-header-container bg-50-grey-20"> - <mat-card-title> - <ng-content select="[region-of-interest]"></ng-content> - <ng-container *ngTemplateOutlet="nameTmpl"> - </ng-container> - </mat-card-title> - - <mat-card-subtitle class="d-inline-flex align-items-center"> - <mat-icon fontSet="fas" fontIcon="fa-database"></mat-icon> - <span> - {{ dataType }} - </span> - - <button *ngIf="isGdprProtected" - [matTooltip]="CONST.GDPR_TEXT" - mat-icon-button color="warn"> - <i class="fas fa-exclamation-triangle"></i> - </button> - - <mat-divider [vertical]="true" class="ml-2 h-2rem"></mat-divider> - - <!-- explore btn --> - <a *ngFor="let kgRef of (urls || [])" - [href]="kgRef.doi | doiParserPipe" - class="color-inherit" - mat-icon-button - [matTooltip]="ARIA_LABELS.EXPLORE_DATASET_IN_KG" - target="_blank"> - <i class="fas fa-external-link-alt"></i> - </a> - - </mat-card-subtitle> - </div> - - </mat-card> - - <!-- description --> - <div class="text-muted d-block mat-body m-4 desc-container" - *ngIf="!loadingFlag"> - <ng-container *ngTemplateOutlet="descTmpl"> - </ng-container> - </div> - - <div> - <ng-container *ngTemplateOutlet="insertedView"> - </ng-container> - </div> -</ng-template> - -<ng-template #nameTmpl> - <span *ngIf="!loadingFlag; else isLoadingTmpl"> - {{ name || nameFallback }} - </span> -</ng-template> - -<!-- desc --> -<ng-template #descTmpl> - <markdown-dom - [markdown]="description || descriptionFallback"> - </markdown-dom> -</ng-template> - -<!-- inserted view --> -<ng-template #insertedView> - <ng-template #insertViewTarget> - - </ng-template> -</ng-template> - -<!-- is loading tmpl --> -<ng-template #isLoadingTmpl> - <spinner-cmp></spinner-cmp> -</ng-template> diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/index.ts b/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/index.ts deleted file mode 100644 index d38b8440733fc665a74c75e3d9fa574ec90dfe26..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { GenericInfoModule } from './module' -export { GenericInfoCmp, TInjectableData } from './genericInfoCmp/genericInfo.component' diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/module.ts b/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/module.ts deleted file mode 100644 index 7f1391e10a378a65c95fd3c8261a5d35509106bf..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/genericInfo/module.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, Inject, NgModule, Optional } from "@angular/core"; -import { MAT_DIALOG_DATA } from "@angular/material/dialog"; -import { ComponentsModule } from "src/components"; -import { AngularMaterialModule } from "src/sharedModules"; -import { UtilModule } from "src/util"; -import { IAV_DATASET_SHOW_DATASET_DIALOG_CMP } from "../kgDataset/showDataset/showDataset.directive"; -import { GenericInfoCmp } from "./genericInfoCmp/genericInfo.component"; - -@Component({ - selector: 'show-ds-dialog-cmp', - template: ` -<ng-template [ngIf]="useClassicUi" [ngIfElse]="modernUiTmpl"> - <generic-info-cmp></generic-info-cmp> -</ng-template> - -<ng-template #modernUiTmpl> - - <mat-dialog-content class="m-0 p-0"> - <generic-info-cmp></generic-info-cmp> - </mat-dialog-content> - - <mat-dialog-actions align="center"> - <button mat-button mat-dialog-close> - Close - </button> - </mat-dialog-actions> - -</ng-template> -` -}) - -export class ShowDsDialogCmp{ - public useClassicUi = false - constructor( - @Optional() @Inject(MAT_DIALOG_DATA) data: any - ){ - this.useClassicUi = data.useClassicUi - } -} - -@NgModule({ - imports: [ - CommonModule, - AngularMaterialModule, - UtilModule, - ComponentsModule, - ], - declarations: [ - GenericInfoCmp, - ShowDsDialogCmp, - ], - exports: [ - GenericInfoCmp, - ], - - providers: [ - { - provide: IAV_DATASET_SHOW_DATASET_DIALOG_CMP, - useValue: ShowDsDialogCmp - } - ] -}) - -export class GenericInfoModule{} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.component.ts deleted file mode 100644 index 1694e857d446519312a46d43254edd66323627ee..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.component.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { Component, Inject, OnDestroy, Optional } from "@angular/core"; -import { Store } from "@ngrx/store"; -import { BehaviorSubject, forkJoin, merge, Observable, of, Subscription } from "rxjs"; -import { catchError, mapTo, switchMap } from "rxjs/operators"; -import { viewerStateAddUserLandmarks, viewerStateChangeNavigation, viewreStateRemoveUserLandmarks } from "src/services/state/viewerState/actions"; -import { BsRegionInputBase } from "../../bsRegionInputBase"; -import { REGISTERED_FEATURE_INJECT_DATA } from "../../constants"; -import { BsFeatureService, TFeatureCmpInput } from "../../service"; -import { SIIBRA_FEATURE_KEY, TContactPoint, TElectrode, TBSIeegSessionDetail } from '../type' -import { ARIA_LABELS, CONST } from 'common/constants' - -@Component({ - selector: 'bs-feature-ieeg.cmp', - templateUrl: './ieeg.template.html', - styleUrls: [ - './ieeg.style.css' - ] -}) - -export class BsFeatureIEEGCmp extends BsRegionInputBase implements OnDestroy{ - - public ARIA_LABELS = ARIA_LABELS - public CONST = CONST - - private featureId: string - - private results: TBSIeegSessionDetail[] = [] - constructor( - private store: Store<any>, - svc: BsFeatureService, - @Optional() @Inject(REGISTERED_FEATURE_INJECT_DATA) data: TFeatureCmpInput, - ){ - super(svc, data) - if (data.featureId) this.featureId = data.featureId - this.subs.push( - this.results$.subscribe(results => { - this.results = results - this.loadLandmarks() - }) - ) - } - - public results$: Observable<TBSIeegSessionDetail[]> = this.region$.pipe( - switchMap(() => this.getFeatureInstancesList(SIIBRA_FEATURE_KEY).pipe( - switchMap(arr => forkJoin(arr.filter(it => { - if (!this.featureId) return true - return it['@id'] === this.featureId - }).map(it => this.getFeatureInstance(SIIBRA_FEATURE_KEY, it["@id"])))), - catchError(() => of([])) - )), - ) - - public busy$ = this.region$.pipe( - switchMap(() => merge( - of(true), - this.results$.pipe( - mapTo(false) - ) - )), - ) - - private subs: Subscription[] = [] - ngOnDestroy(){ - this.unloadLandmarks() - while(this.subs.length) this.subs.pop().unsubscribe() - } - private openElectrodeSet = new Set<TElectrode>() - public openElectrode$ = new BehaviorSubject<TElectrode[]>([]) - handleDatumExpansion(electrode: TElectrode, state: boolean) { - if (state) this.openElectrodeSet.add(electrode) - else this.openElectrodeSet.delete(electrode) - this.openElectrode$.next(Array.from(this.openElectrodeSet)) - this.loadLandmarks() - } - - private loadedLms: { - '@id': string - id: string - name: string - position: [number, number, number] - color: [number, number, number] - showInSliceView: boolean - }[] = [] - - private unloadLandmarks(){ - /** - * unload all the landmarks first - */ - this.store.dispatch( - viewreStateRemoveUserLandmarks({ - payload: { - landmarkIds: this.loadedLms.map(l => l['@id']) - } - }) - ) - } - - private loadLandmarks(){ - this.unloadLandmarks() - this.loadedLms = [] - - const lms = [] as { - '@id': string - id: string - name: string - position: [number, number, number] - color: [number, number, number] - showInSliceView: boolean - }[] - - for (const detail of this.results) { - const subjectKey = detail.sub_id - for (const electrodeId in detail.electrodes){ - const electrode = detail.electrodes[electrodeId] - if (!electrode.inRoi) continue - for (const cpKey in electrode.contact_points) { - const cp = electrode.contact_points[cpKey] - const id=`${detail.name}:${subjectKey}#${cpKey}` - lms.push({ - "@id": id, - id: id, - name: id, - position: cp.location, - color: cp.inRoi ? [255, 100, 100]: [255, 255, 255], - showInSliceView: this.openElectrodeSet.has(electrode) - }) - } - } - } - this.loadedLms = lms - - this.store.dispatch( - viewerStateAddUserLandmarks({ - landmarks: lms - }) - ) - } - - handleContactPtClk(cp: TContactPoint) { - const { location } = cp - this.store.dispatch( - viewerStateChangeNavigation({ - navigation: { - position: location.map(v => v * 1e6), - positionReal: true, - animation: {} - }, - }) - ) - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.template.html deleted file mode 100644 index 2622d200acae890dc47352ef6f0859ccc7125369..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.template.html +++ /dev/null @@ -1,67 +0,0 @@ -<ng-template [ngIf]="busy$ | async" [ngIfElse]="contenttmpl"> - <spinner-cmp></spinner-cmp> -</ng-template> - -<ng-template #contenttmpl> - <ng-container *ngFor="let result of results$ | async"> - <ng-container *ngFor="let electrodeKeyVal of result | getProperty : 'electrodes' | keyvalue"> - <ng-template [ngIf]="electrodeKeyVal.value.inRoi"> - <ng-container *ngTemplateOutlet="electrodeTmpl; context: { $implicit: electrodeKeyVal.value }"> - </ng-container> - </ng-template> - - </ng-container> - </ng-container> -</ng-template> - -<!-- template for electrode --> -<ng-template #electrodeTmpl let-electrode> - - <mat-expansion-panel - [expanded]="openElectrode$ | async | includes : electrode" - (opened)="handleDatumExpansion(electrode, true)" - (closed)="handleDatumExpansion(electrode, false)" - togglePosition="before"> - <mat-expansion-panel-header> - <mat-panel-title> - Electrode - </mat-panel-title> - <mat-panel-description class="text-nowrap"> - {{ electrode.electrode_id }} - </mat-panel-description> - </mat-expansion-panel-header> - - - <!-- <label for="task-list" class="d-block mat-h4 mt-4 text-muted"> - Tasks - </label> - <section class="d-flex align-items-center mt-1"> - <section id="task-list" class="flex-grow-1 flex-shrink-1 overflow-x-auto"> - <div role="list"> - <mat-chip *ngFor="let task of datum['tasks']" class="ml-1"> - {{ task }} - </mat-chip> - </div> - </section> - </section> --> - - <mat-divider></mat-divider> - - <label for="contact-points-list" class="d-block mat-h4 mt-4 text-muted"> - Contact Points - </label> - <section class="d-flex align-items-center mt-1"> - <section id="contact-points-list" class="flex-grow-1 flex-shrink-1 overflow-x-auto"> - <div role="list"> - <mat-chip *ngFor="let cp_kv of electrode.contact_points | keyvalue" - [matTooltip]="cp_kv['value']['location']" - (click)="handleContactPtClk(cp_kv['value'])" - class="ml-1"> - {{ cp_kv['key'] }} - </mat-chip> - </div> - </section> - </section> - - </mat-expansion-panel> -</ng-template> diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCtrl.directive.ts b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCtrl.directive.ts deleted file mode 100644 index e7e79ef4a97103ec38993712490cd0ebbb6ebb1b..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCtrl.directive.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Directive, Inject, OnDestroy, Optional } from "@angular/core"; -import { merge, Observable, of, Subscription } from "rxjs"; -import { catchError, mapTo, switchMap } from "rxjs/operators"; -import { BsRegionInputBase } from "../bsRegionInputBase"; -import { REGISTERED_FEATURE_INJECT_DATA } from "../constants"; -import { BsFeatureService, TFeatureCmpInput } from "../service"; -import { IBSSummaryResponse, IRegionalFeatureReadyDirective } from "../type"; -import { SIIBRA_FEATURE_KEY } from './type' - -@Directive({ - selector: '[bs-features-ieeg-directive]', - exportAs: 'bsFeatureIeegDirective' -}) - -export class BsFeatureIEEGDirective extends BsRegionInputBase implements IRegionalFeatureReadyDirective, OnDestroy{ - - public results$: Observable<IBSSummaryResponse['IEEG_Session'][]> = this.region$.pipe( - switchMap(() => this.getFeatureInstancesList(SIIBRA_FEATURE_KEY).pipe( - catchError(() => of([])) - )), - ) - public busy$ = this.region$.pipe( - switchMap(() => merge( - of(true), - this.results$.pipe( - mapTo(false) - ) - )) - ) - - constructor( - svc: BsFeatureService, - @Optional() @Inject(REGISTERED_FEATURE_INJECT_DATA) data: TFeatureCmpInput, - ){ - super(svc, data) - } - - private sub: Subscription[] = [] - ngOnDestroy(){ - while(this.sub.length) this.sub.pop().unsubscribe() - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/index.ts b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/index.ts deleted file mode 100644 index e4e8322a07b0f6a2a25ce0b8522e5d2550b2f09b..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { - BsFeatureIEEGModule -} from './module' - -export { - IEEG_FEATURE_NAME, - SIIBRA_FEATURE_KEY, -} from './type' diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/module.ts b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/module.ts deleted file mode 100644 index 50ea68c3c313509efacea5473ad554e181bddc40..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/module.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { ComponentsModule } from "src/components"; -import { AngularMaterialModule } from "src/sharedModules"; -import { UtilModule } from "src/util"; -import { BsFeatureService } from "../service"; -import { BsFeatureIEEGCmp } from "./ieegCmp/ieeg.component"; -import { BsFeatureIEEGDirective } from "./ieegCtrl.directive"; -import { IEEG_FEATURE_NAME } from "./type"; - -@NgModule({ - imports: [ - CommonModule, - ComponentsModule, - UtilModule, - AngularMaterialModule, - ], - declarations: [ - BsFeatureIEEGCmp, - BsFeatureIEEGDirective - ] -}) - -export class BsFeatureIEEGModule{ - constructor(svc: BsFeatureService){ - svc.registerFeature({ - name: IEEG_FEATURE_NAME, - icon: 'fas fa-info', - View: BsFeatureIEEGCmp, - Ctrl: BsFeatureIEEGDirective - }) - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/type.ts b/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/type.ts deleted file mode 100644 index 8300f694dde812df13206c56c403bd06ebb59be7..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/type.ts +++ /dev/null @@ -1,45 +0,0 @@ -export type TBSSummary = { - '@id': string - name: string - description: string -} - -export type TContactPoint = { - id: string - location: [number, number, number] - inRoi?: boolean -} - -export type TElectrode = { - electrode_id: string - subject_id: string - contact_points: { - [key: string]: TContactPoint - } - inRoi?: boolean -} - -export type TBSIeegSessionSummary = { - '@id': string - name: string - description: string - origin_datainfos: { - urls: { - doi: string - }[] - }[] -} - -type TDetail = { - sub_id: string - electrodes: { - [key: string]: TElectrode - } - inRoi?: boolean -} - -export type TBSIeegSessionDetail = TBSIeegSessionSummary & TDetail - -export const SIIBRA_FEATURE_KEY = 'IEEG_Session' -export const _SIIBRA_FEATURE_KEY = 'IEEG_Electrode' -export const IEEG_FEATURE_NAME = 'iEEG recordings' diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/index.ts b/src/atlasComponents/regionalFeatures/bsFeatures/index.ts deleted file mode 100644 index e571d3c4272b3c27af2343832de0f8d8aabd2399..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { BSFeatureModule } from './module' -export { BS_ENDPOINT, BS_DARKTHEME } from './constants' -export { TRegion } from './type' -// nb do not export BsRegionInputBase from here -// will result in cyclic imports \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/getTrailingHex.pipe.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/getTrailingHex.pipe.ts deleted file mode 100644 index 5314267e7e1f31b8226ab6eea1ac890277781a68..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/getTrailingHex.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name: 'getTrailingHex', - pure: true -}) - -export class GetTrailingHexPipe implements PipeTransform{ - public transform(input: string) { - const match = /[0-9a-f-]+$/.exec(input) - return match && match[0] - } -} \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/index.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/index.ts deleted file mode 100644 index 337cdf646dd4827cc0f0a9a14f6637d0f4cb129e..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { KgDatasetModule } from './module' -export { TCountedDataModality, TBSDetail, TBSSummary } from './type' \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.component.spec.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.component.spec.ts deleted file mode 100644 index 9618701aaa39492065095230cc37d7b524d3fbec..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.component.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { TCountedDataModality } from "../type" -import { SortModalityAlphabeticallyPipe } from "./modalityPicker.component" - -describe('> modalityPicker.component.ts', () => { - describe('> ModalityPicker', () => { - // TODO - }) - - describe('> SortModalityAlphabeticallyPipe', () => { - - const mods: TCountedDataModality[] = [{ - name: 'bbb', - occurance: 0, - visible: false - }, { - name: 'AAA', - occurance: 1, - visible: false - }, { - name: '007', - occurance: 17, - visible: false - }] - const beforeInput = [...mods] - const pipe = new SortModalityAlphabeticallyPipe() - - const output = pipe.transform(mods) - - it('> does not mutate', () => { - expect(mods).toEqual(beforeInput) - }) - it('> should sort modalities as expected', () => { - expect(output).toEqual([ - mods[2], mods[1], mods[0] - ]) - }) - }) -}) diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.component.ts deleted file mode 100644 index c00a636533018ec616110d4f366d967144254afa..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Component, EventEmitter, Input, OnChanges, Output, Pipe, PipeTransform } from "@angular/core"; -import { TCountedDataModality } from "../type"; -import { ARIA_LABELS } from 'common/constants' - - -@Component({ - selector: 'modality-picker', - templateUrl: './modalityPicker.template.html', - styleUrls: [ - './modalityPicker.style.css', - ], - host:{ - 'aria-label': ARIA_LABELS.LIST_OF_MODALITIES - } -}) - -export class ModalityPicker implements OnChanges { - - public modalityVisibility: Set<string> = new Set() - - @Input() - public countedDataM: TCountedDataModality[] = [] - - public checkedModality: TCountedDataModality[] = [] - - @Output() - public modalityFilterEmitter: EventEmitter<TCountedDataModality[]> = new EventEmitter() - - // filter(dataentries:DataEntry[]) { - // return this.modalityVisibility.size === 0 - // ? dataentries - // : dataentries.filter(de => de.activity.some(a => a.methods.some(m => this.modalityVisibility.has(this.dbService.temporaryFilterDataentryName(m))))) - // } - - public ngOnChanges() { - this.checkedModality = this.countedDataM.filter(d => d.visible) - } - - /** - * TODO - * togglemodailty should emit event, and let parent handle state - */ - public toggleModality(modality: Partial<TCountedDataModality>) { - this.modalityFilterEmitter.emit( - this.countedDataM.map(d => d.name === modality.name - ? { - ...d, - visible: !d.visible, - } - : d), - ) - } - - public uncheckModality(modality: string) { - this.toggleModality({name: modality}) - } - - public clearAll() { - this.modalityFilterEmitter.emit( - this.countedDataM.map(d => { - return { - ...d, - visible: false, - } - }), - ) - } -} - -const sortByFn = (a: TCountedDataModality, b: TCountedDataModality) => (a.name || '0').toLowerCase().charCodeAt(0) - (b.name || '0').toLowerCase().charCodeAt(0) - -@Pipe({ - name: 'sortModalityAlphabetically', - pure: true -}) - -export class SortModalityAlphabeticallyPipe implements PipeTransform{ - public transform(arr: TCountedDataModality[]): TCountedDataModality[]{ - return [...arr].sort(sortByFn) - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.style.css b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.style.css deleted file mode 100644 index 85feb59e81b8a38ede569688b605d82e39932cca..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.style.css +++ /dev/null @@ -1,10 +0,0 @@ -:host -{ - display: flex; - flex-direction: column; -} - -div -{ - white-space: nowrap; -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.template.html deleted file mode 100644 index 7bde04e8b2cc46f0872d978914bf9c254e79113a..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/modalityPicker/modalityPicker.template.html +++ /dev/null @@ -1,14 +0,0 @@ -<fieldset> - <legend class="sr-only"> - Dataset modalities - </legend> - - <mat-checkbox - - [checked]="datamodality.visible" - (change)="toggleModality(datamodality)" - [ngClass]="{'muted': datamodality.occurance === 0}" - *ngFor="let datamodality of countedDataM | sortModalityAlphabetically"> - {{ datamodality.name }} <span class="text-muted">({{ datamodality.occurance }})</span> - </mat-checkbox> -</fieldset> diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/module.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/module.ts deleted file mode 100644 index 232dba3734ce321bc2fd3a9fb1a5c52d0d4c9256..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { ShowDatasetDialogDirective } from "./showDataset/showDataset.directive"; -import { AngularMaterialModule } from "src/sharedModules"; -import { GetTrailingHexPipe } from "./getTrailingHex.pipe"; -import { ModalityPicker, SortModalityAlphabeticallyPipe } from "./modalityPicker/modalityPicker.component"; - -// TODO break down into smaller components -@NgModule({ - imports: [ - CommonModule, - AngularMaterialModule, - ], - declarations: [ - ShowDatasetDialogDirective, - GetTrailingHexPipe, - ModalityPicker, - SortModalityAlphabeticallyPipe, - ], - exports: [ - ShowDatasetDialogDirective, - GetTrailingHexPipe, - ModalityPicker, - ] -}) - -export class KgDatasetModule{} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.spec.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.spec.ts deleted file mode 100644 index e37fa07aa72c7366814b10ecdbebb458118795c5..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.spec.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { Component } from "@angular/core"; -import { async, TestBed } from "@angular/core/testing"; -import { AngularMaterialModule } from "src/sharedModules"; -import { ShowDatasetDialogDirective, IAV_DATASET_SHOW_DATASET_DIALOG_CMP } from "./showDataset.directive"; -import { By } from "@angular/platform-browser"; -import { MatDialog } from "@angular/material/dialog"; -import { MatSnackBar } from "@angular/material/snack-bar"; - -@Component({ - template: '' -}) - -class TestCmp{} - -const dummyMatDialog = { - open: val => {} -} - -const dummyMatSnackBar = { - open: val => {} -} - -class DummyDialogCmp{} - -describe('ShowDatasetDialogDirective', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - AngularMaterialModule - ], - declarations: [ - TestCmp, - ShowDatasetDialogDirective, - ], - providers: [ - { - provide: MatDialog, - useValue: dummyMatDialog - }, - { - provide: MatSnackBar, - useValue: dummyMatSnackBar - }, - { - provide: IAV_DATASET_SHOW_DATASET_DIALOG_CMP, - useValue: DummyDialogCmp - } - ] - }) - })) - - it('should be able to test directiv,', () => { - - TestBed.overrideComponent(TestCmp, { - set: { - template: '<div iav-dataset-show-dataset-dialog></div>' - } - }).compileComponents() - - const fixutre = TestBed.createComponent(TestCmp) - const directive = fixutre.debugElement.query( By.directive( ShowDatasetDialogDirective ) ) - - expect(directive).not.toBeNull() - }) - - it('if neither kgId nor fullId is defined, should not call dialog', () => { - - TestBed.overrideComponent(TestCmp, { - set: { - template: '<div iav-dataset-show-dataset-dialog></div>' - } - }).compileComponents() - - const snackbarOpenSpy = spyOn(dummyMatSnackBar, 'open').and.callThrough() - const dialogOpenSpy = spyOn(dummyMatDialog, 'open').and.callThrough() - - const fixutre = TestBed.createComponent(TestCmp) - fixutre.detectChanges() - - const directive = fixutre.debugElement.query( By.directive( ShowDatasetDialogDirective ) ) - directive.nativeElement.click() - - expect(snackbarOpenSpy).toHaveBeenCalled() - expect(dialogOpenSpy).not.toHaveBeenCalled() - - snackbarOpenSpy.calls.reset() - dialogOpenSpy.calls.reset() - }) - - it('if kgId is defined, should call dialogOpen', () => { - - TestBed.overrideComponent(TestCmp, { - set: { - template: ` - <div iav-dataset-show-dataset-dialog - iav-dataset-show-dataset-dialog-kgid="aaa-bbb"> - </div> - ` - } - }).compileComponents() - - const snackbarOpenSpy = spyOn(dummyMatSnackBar, 'open').and.callThrough() - const dialogOpenSpy = spyOn(dummyMatDialog, 'open').and.callThrough() - - const fixutre = TestBed.createComponent(TestCmp) - fixutre.detectChanges() - - const directive = fixutre.debugElement.query( By.directive( ShowDatasetDialogDirective ) ) - directive.nativeElement.click() - - expect(snackbarOpenSpy).not.toHaveBeenCalled() - const mostRecentCall = dialogOpenSpy.calls.mostRecent() - const args = mostRecentCall.args as any[] - - expect(args[0]).toEqual(DummyDialogCmp) - expect(args[1]).toEqual({ - ...ShowDatasetDialogDirective.defaultDialogConfig, - panelClass: ['no-padding-dialog'], - data: { - fullId: `minds/core/dataset/v1.0.0/aaa-bbb` - } - }) - - snackbarOpenSpy.calls.reset() - dialogOpenSpy.calls.reset() - }) - - it('if fullId is defined, should call dialogOpen', () => { - - TestBed.overrideComponent(TestCmp, { - set: { - template: ` - <div iav-dataset-show-dataset-dialog - iav-dataset-show-dataset-dialog-fullid="abc/ccc-ddd"> - </div> - ` - } - }).compileComponents() - - const snackbarOpenSpy = spyOn(dummyMatSnackBar, 'open').and.callThrough() - const dialogOpenSpy = spyOn(dummyMatDialog, 'open').and.callThrough() - - const fixutre = TestBed.createComponent(TestCmp) - fixutre.detectChanges() - - const directive = fixutre.debugElement.query( By.directive( ShowDatasetDialogDirective ) ) - directive.nativeElement.click() - - expect(snackbarOpenSpy).not.toHaveBeenCalled() - const mostRecentCall = dialogOpenSpy.calls.mostRecent() - const args = mostRecentCall.args as any[] - expect(args[0]).toEqual(DummyDialogCmp) - expect(args[1]).toEqual({ - ...ShowDatasetDialogDirective.defaultDialogConfig, - panelClass: ['no-padding-dialog'], - data: { - fullId: `abc/ccc-ddd` - } - }) - - snackbarOpenSpy.calls.reset() - dialogOpenSpy.calls.reset() - }) -}) \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.ts deleted file mode 100644 index d4b02e120d85776b02b0db606da5aa059cd46113..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/showDataset/showDataset.directive.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Directive, HostListener, Inject, Input, Optional } from "@angular/core"; -import { MatDialog, MatDialogConfig } from "@angular/material/dialog"; -import { MatSnackBar } from "@angular/material/snack-bar"; -import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, TOverwriteShowDatasetDialog } from "src/util/interfaces"; -import { TRegionDetail as TSiibraRegion } from "src/util/siibraApiConstants/types"; -import { TRegion as TContextRegion } from 'src/atlasComponents/regionalFeatures/bsFeatures/type' - -export const IAV_DATASET_SHOW_DATASET_DIALOG_CMP = 'IAV_DATASET_SHOW_DATASET_DIALOG_CMP' -export const IAV_DATASET_SHOW_DATASET_DIALOG_CONFIG = `IAV_DATASET_SHOW_DATASET_DIALOG_CONFIG` - -@Directive({ - selector: '[iav-dataset-show-dataset-dialog]', - exportAs: 'iavDatasetShowDatasetDialog' -}) -export class ShowDatasetDialogDirective{ - - static defaultDialogConfig: MatDialogConfig = { - autoFocus: false - } - - @Input('iav-dataset-show-dataset-dialog-name') - name: string - - @Input('iav-dataset-show-dataset-dialog-description') - description: string - - @Input('iav-dataset-show-dataset-dialog-kgschema') - kgSchema: string = 'minds/core/dataset/v1.0.0' - - @Input('iav-dataset-show-dataset-dialog-kgid') - kgId: string - - @Input('iav-dataset-show-dataset-dialog-fullid') - fullId: string - - @Input('iav-dataset-show-dataset-dialog-urls') - urls: { - cite: string - doi: string - }[] = [] - - @Input('iav-dataset-show-dataset-dialog-ignore-overwrite') - ignoreOverwrite = false - - @Input('iav-dataset-show-dataset-dialog-contexted-region') - region: TSiibraRegion & TContextRegion - - constructor( - private matDialog: MatDialog, - private snackbar: MatSnackBar, - @Optional() @Inject(IAV_DATASET_SHOW_DATASET_DIALOG_CMP) private dialogCmp: any, - @Optional() @Inject(OVERWRITE_SHOW_DATASET_DIALOG_TOKEN) private overwriteFn: TOverwriteShowDatasetDialog - ){ } - - @HostListener('click') - onClick(){ - const data = (() => { - if (this.fullId || (this.kgSchema && this.kgId)) { - return { - fullId: this.fullId || `${this.kgSchema}/${this.kgId}` - } - } - if (this.name || this.description) { - const { name, description, urls } = this - return { name, description, urls, useClassicUi: true } - } - })() - - if (!data) { - return this.snackbar.open(`Cannot show dataset. Neither fullId nor kgId provided.`) - } - - if (!this.ignoreOverwrite && this.overwriteFn) { - return this.overwriteFn(data) - } - - if (!this.dialogCmp) throw new Error(`IAV_DATASET_SHOW_DATASET_DIALOG_CMP not provided!`) - const { useClassicUi } = data - this.matDialog.open(this.dialogCmp, { - ...ShowDatasetDialogDirective.defaultDialogConfig, - data, - ...(useClassicUi ? {} : { panelClass: ['no-padding-dialog'] }) - }) - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/type.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/type.ts deleted file mode 100644 index e56921ce5c5e1e9adc110ba666dcf1d2eb192be3..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgDataset/type.ts +++ /dev/null @@ -1,82 +0,0 @@ -export type TCountedDataModality = { - name: string - occurance: number - visible: boolean -} - -export type TBSSummary = { - ['@id']: string - src_name: string -} - -export type TBSDetail = TBSSummary & { - __detail: { - formats: string[] - datasetDOI: { - cite: string - doi: string - }[] - activity: { - protocols: string[] - preparation: string[] - }[] - referenceSpaces: { - name: string - fullId: string - }[] - methods: string[] - custodians: { - "schema.org/shortName": string - identifier: string - name: string - '@id': string - shortName: string - }[] - project: string[] - description: string - parcellationAtlas: { - name: string - fullId: string - id: string[] - }[] - licenseInfo: { - name: string - url: string - }[] - embargoStatus: { - identifier: string[] - name: string - '@id': string - }[] - license: any[] - parcellationRegion: { - species: any[] - name: string - alias: string - fullId: string - }[] - species: string[] - name: string - files: { - byteSize: number - name: string - absolutePath: string - contentType: string - }[] - fullId: string - contributors: { - "schema.org/shortName": string - identifier: string - name: string - '@id': string - shortName: string - }[] - id: string - kgReference: string[] // aka doi - publications: { - name: string - cite: string - doi: string - }[] - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/index.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/index.ts deleted file mode 100644 index 3efa1d80ed5927d3777da4cc28faad043ce7795b..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { - KgRegionalFeatureModule -} from './module' - -export { - EbrainsRegionalFeatureName, - KG_REGIONAL_FEATURE_KEY, - UNDER_REVIEW, - TBSDetail, - TBSSummary -} from './type' diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgRegList.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgRegList.component.ts deleted file mode 100644 index fd5b0afac18cbfd515e7ef21718e71084f75c0b2..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgRegList.component.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { ChangeDetectionStrategy, Component, Inject, Input, OnDestroy, Optional } from "@angular/core"; -import { BehaviorSubject, Subscription } from "rxjs"; -import { filter, switchMap, tap } from "rxjs/operators"; -import { TCountedDataModality } from '../../kgDataset' -import { BsRegionInputBase } from "../../bsRegionInputBase"; -import { BsFeatureService, TFeatureCmpInput } from "../../service"; -import { KG_REGIONAL_FEATURE_KEY, TBSDetail, TBSSummary } from "../type"; -import { ARIA_LABELS } from 'common/constants' -import { REGISTERED_FEATURE_INJECT_DATA } from "../../constants"; - -@Component({ - selector: 'kg-regional-features-list', - templateUrl: './kgRegList.template.html', - styleUrls: [ - './kgRegList.style.css' - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class KgRegionalFeaturesList extends BsRegionInputBase implements OnDestroy{ - - public ARIA_LABELS = ARIA_LABELS - - public dataModalities: TCountedDataModality[] = [] - - @Input() - public disableVirtualScroll = false - - public visibleRegionalFeatures: TBSSummary[] = [] - public kgRegionalFeatures: TBSSummary[] = [] - public kgRegionalFeatures$ = this.region$.pipe( - filter(v => { - this.busy$.next(false) - return !!v - }), - // must not use switchmapto here - switchMap(() => { - this.busy$.next(true) - return this.getFeatureInstancesList(KG_REGIONAL_FEATURE_KEY).pipe( - tap(() => { - this.busy$.next(false) - }) - ) - }) - ) - constructor( - svc: BsFeatureService, - @Optional() @Inject(REGISTERED_FEATURE_INJECT_DATA) data: TFeatureCmpInput - ){ - super(svc, data) - this.sub.push( - this.kgRegionalFeatures$.subscribe(val => { - this.kgRegionalFeatures = val - this.visibleRegionalFeatures = val - }) - ) - } - private sub: Subscription[] = [] - ngOnDestroy(){ - while (this.sub.length) this.sub.pop().unsubscribe() - } - - public trackByFn(_index: number, dataset: TBSSummary) { - return dataset['@id'] - } - - public detailDict: { - [key: string]: TBSDetail - } = {} - - public handlePopulatedDetailEv(detail: TBSDetail){ - this.detailDict = { - ...this.detailDict, - [detail["@id"]]: detail - } - for (const method of detail.__detail.methods) { - const found = this.dataModalities.find(v => v.name === method) - if (found) found.occurance = found.occurance + 1 - else this.dataModalities.push({ - name: method, - occurance: 1, - visible: false - }) - } - this.dataModalities = [...this.dataModalities] - } - - public busy$ = new BehaviorSubject(false) -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgRegList.style.css b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgRegList.style.css deleted file mode 100644 index a0d49c32422a4789531e0cdaf7bf02fd4d43e20f..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgRegList.style.css +++ /dev/null @@ -1,9 +0,0 @@ -cdk-virtual-scroll-viewport -{ - min-height: 24rem; -} - -modality-picker -{ - font-size: 90%; -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgRegList.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgRegList.template.html deleted file mode 100644 index 010f4c85971f683525a8f3a6ca7026d648ea0e45..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgRegList.template.html +++ /dev/null @@ -1,30 +0,0 @@ -<spinner-cmp *ngIf="busy$ | async; else contentTmpl"></spinner-cmp> - -<ng-template #contentTmpl> - <cdk-virtual-scroll-viewport - [attr.aria-label]="ARIA_LABELS.LIST_OF_DATASETS_ARIA_LABEL" - class="h-100" - minBufferPx="200" - maxBufferPx="400" - itemSize="50"> - <div *cdkVirtualFor="let dataset of visibleRegionalFeatures; trackBy: trackByFn; templateCacheSize: 20; let index = index" - class="h-50px overflow-hidden"> - - <!-- divider, show if not first --> - <mat-divider class="mt-1" *ngIf="index !== 0"></mat-divider> - - <kg-regional-feature-summary - mat-ripple - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-fullid]="dataset['@id']" - [iav-dataset-show-dataset-dialog-contexted-region]="region" - class="d-block pb-1 pt-1" - [region]="region" - [loadFull]="false" - [summary]="dataset" - (loadedDetail)="handlePopulatedDetailEv($event)"> - </kg-regional-feature-summary> - - </div> - </cdk-virtual-scroll-viewport> -</ng-template> \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgReglist.directive.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgReglist.directive.ts deleted file mode 100644 index df51bbb7f7c90d589e6e5c843d25f3a7f711f027..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegList/kgReglist.directive.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Directive, EventEmitter, Inject, OnDestroy, Optional, Output } from "@angular/core"; -import { KG_REGIONAL_FEATURE_KEY, TBSSummary } from "../type"; -import { BsFeatureService, TFeatureCmpInput } from "../../service"; -import { BsRegionInputBase } from "../../bsRegionInputBase"; -import { merge, of, Subscription } from "rxjs"; -import { catchError, mapTo, startWith, switchMap, tap } from "rxjs/operators"; -import { IRegionalFeatureReadyDirective } from "../../type"; -import { REGISTERED_FEATURE_INJECT_DATA } from "../../constants"; - -@Directive({ - selector: '[kg-regional-features-list-directive]', - exportAs: 'kgRegionalFeaturesListDirective' -}) - -export class KgRegionalFeaturesListDirective extends BsRegionInputBase implements IRegionalFeatureReadyDirective, OnDestroy { - public kgRegionalFeatures: TBSSummary[] = [] - public kgRegionalFeatures$ = this.region$.pipe( - // must not use switchmapto here - switchMap(() => { - this.busyEmitter.emit(true) - return this.getFeatureInstancesList(KG_REGIONAL_FEATURE_KEY).pipe( - catchError(() => of([])), - tap(() => { - this.busyEmitter.emit(false) - }), - ) - }), - startWith([]) - ) - - constructor( - svc: BsFeatureService, - @Optional() @Inject(REGISTERED_FEATURE_INJECT_DATA) data: TFeatureCmpInput, - ){ - super(svc, data) - this.sub.push( - this.kgRegionalFeatures$.subscribe(val => { - this.kgRegionalFeatures = val - }) - ) - } - private sub: Subscription[] = [] - ngOnDestroy(){ - while (this.sub.length) this.sub.pop().unsubscribe() - } - - results$ = this.kgRegionalFeatures$ - busy$ = this.region$.pipe( - switchMap(() => merge( - of(true), - this.results$.pipe( - mapTo(false) - ) - )) - ) - - @Output('kg-regional-features-list-directive-busy') - busyEmitter = new EventEmitter<boolean>() -} \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegSummary/kgRegSummary.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegSummary/kgRegSummary.component.ts deleted file mode 100644 index 841226ad69f75fd821954f624878c4e1a4081de6..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegSummary/kgRegSummary.component.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Component, EventEmitter, Input, OnChanges, Output } from "@angular/core"; -import { BsRegionInputBase } from "../../bsRegionInputBase"; -import { BsFeatureService } from "../../service"; -import { KG_REGIONAL_FEATURE_KEY, TBSDetail, TBSSummary } from '../type' - -@Component({ - selector: 'kg-regional-feature-summary', - templateUrl: './kgRegSummary.template.html', - styleUrls: [ - './kgRegSummary.style.css' - ], - exportAs: 'kgRegionalFeatureSummary' -}) - -export class KgRegSummaryCmp extends BsRegionInputBase implements OnChanges{ - - @Input() - public loadFull = false - - @Input() - public summary: TBSSummary = null - - public detailLoaded = false - public loadingDetail = false - public detail: TBSDetail = null - @Output() - public loadedDetail = new EventEmitter<TBSDetail>() - - public error: string = null - @Output() - public errorEmitter = new EventEmitter<string>() - - constructor(svc: BsFeatureService){ - super(svc) - } - - ngOnChanges(){ - if (this.loadFull && !!this.summary) { - if (this.loadingDetail || this.detailLoaded) { - return - } - this.loadingDetail = true - this.getFeatureInstance(KG_REGIONAL_FEATURE_KEY, this.summary["@id"]).subscribe( - detail => { - this.detail = detail - this.loadedDetail.emit(detail) - }, - err => { - this.error = err - this.errorEmitter.emit(err) - }, - () => { - this.detailLoaded = true - this.loadingDetail = false - } - ) - } - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegSummary/kgRegSummary.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegSummary/kgRegSummary.template.html deleted file mode 100644 index 5fcb11b2b8f7a550e66a3491430a0629d62f7533..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegSummary/kgRegSummary.template.html +++ /dev/null @@ -1,3 +0,0 @@ -<small> - {{ summary.src_name }} -</small> diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/module.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/module.ts deleted file mode 100644 index 878eed872ca8ce48c90e6931eeb4351a2fd44892..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/module.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Inject, NgModule, Optional } from "@angular/core"; -import { AngularMaterialModule } from "src/sharedModules"; -import { KgRegSummaryCmp } from "./kgRegSummary/kgRegSummary.component"; -import { KgRegionalFeaturesList } from "./kgRegList/kgRegList.component"; -import { KgRegionalFeaturesListDirective } from "./kgRegList/kgReglist.directive"; -import { KgDatasetModule } from "../kgDataset"; -import { UtilModule } from "src/util"; -import { ComponentsModule } from "src/components"; -import { BsFeatureService } from "../service"; -import { EbrainsRegionalFeatureName } from "./type"; -import { GENERIC_INFO_INJ_TOKEN } from "../type"; - -@NgModule({ - imports: [ - CommonModule, - AngularMaterialModule, - KgDatasetModule, - UtilModule, - ComponentsModule, - ], - declarations:[ - KgRegSummaryCmp, - KgRegionalFeaturesList, - KgRegionalFeaturesListDirective, - ], - exports:[ - KgRegSummaryCmp, - KgRegionalFeaturesList, - KgRegionalFeaturesListDirective, - ], -}) - -export class KgRegionalFeatureModule{ - constructor( - svc: BsFeatureService, - @Optional() @Inject(GENERIC_INFO_INJ_TOKEN) Cmp: any - ){ - if (!Cmp) { - console.warn(`GENERIC_INFO_INJ_TOKEN not injected!`) - return - } - svc.registerFeature({ - name: EbrainsRegionalFeatureName, - icon: 'fas fa-ellipsis-h', - View: null, - Ctrl: KgRegionalFeaturesListDirective, - }) - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/type.ts b/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/type.ts deleted file mode 100644 index f7ebe60367da3805b95b90c03237f31d4a23b4f2..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/type.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { - TBSDetail, TBSSummary -} from '../kgDataset' - -export const EbrainsRegionalFeatureName = 'EBRAINS datasets' -export const KG_REGIONAL_FEATURE_KEY = 'EbrainsRegionalDataset' - -export const UNDER_REVIEW = { - ['@id']: "https://nexus.humanbrainproject.org/v0/data/minds/core/embargostatus/v1.0.0/1d726b76-b176-47ed-96f0-b4f2e17d5f19" -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/module.ts b/src/atlasComponents/regionalFeatures/bsFeatures/module.ts deleted file mode 100644 index 20939be689ba1885ac2129c9d8f24e10743df630..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/module.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { ComponentsModule } from "src/components"; -import { AngularMaterialModule } from "src/sharedModules"; -import { GenericInfoCmp, GenericInfoModule } from "./genericInfo"; -import { BsFeatureIEEGModule } from "./ieeg/module"; -import { KgRegionalFeatureModule } from "./kgRegionalFeature"; -import { GetBadgeFromFeaturePipe } from "./pipes/getBadgeFromFeature.pipe"; -import { RenderRegionalFeatureSummaryPipe } from "./pipes/renderRegionalFeatureSummary.pipe"; -import { BSFeatureReceptorModule } from "./receptor"; -import { RegionalFeatureWrapperCmp } from "./regionalFeatureWrapper/regionalFeatureWrapper.component"; -import { BsFeatureService } from "./service"; -import { GENERIC_INFO_INJ_TOKEN } from "./type"; - -@NgModule({ - imports: [ - AngularMaterialModule, - CommonModule, - KgRegionalFeatureModule, - BSFeatureReceptorModule, - BsFeatureIEEGModule, - ComponentsModule, - GenericInfoModule, - ], - declarations: [ - RegionalFeatureWrapperCmp, - RenderRegionalFeatureSummaryPipe, - GetBadgeFromFeaturePipe, - ], - providers: [ - BsFeatureService, - { - provide: GENERIC_INFO_INJ_TOKEN, - useValue: GenericInfoCmp - } - ], - exports: [ - RegionalFeatureWrapperCmp, - GenericInfoModule, - ] -}) - -export class BSFeatureModule{} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/pipes/getBadgeFromFeature.pipe.ts b/src/atlasComponents/regionalFeatures/bsFeatures/pipes/getBadgeFromFeature.pipe.ts deleted file mode 100644 index 8fe67e163d116fb68ccbbff6524cb21d544db3f2..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/pipes/getBadgeFromFeature.pipe.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { IBSSummaryResponse, TContextedFeature } from "../type"; -import { - IEEG_FEATURE_NAME -} from '../ieeg' -import { - RECEPTOR_FEATURE_NAME -} from '../receptor' - -export type TBadge = { - text: string - color: 'primary' | 'warn' | 'accent' -} - -@Pipe({ - name: 'getBadgeFromFeaturePipe', - pure: true -}) - -export class GetBadgeFromFeaturePipe implements PipeTransform{ - public transform(input: TContextedFeature<keyof IBSSummaryResponse>): TBadge[]{ - if (input.featureName === IEEG_FEATURE_NAME) { - return [{ - text: IEEG_FEATURE_NAME, - color: 'primary', - }] - } - if (input.featureName === RECEPTOR_FEATURE_NAME) { - return [{ - text: RECEPTOR_FEATURE_NAME, - color: 'accent', - }] - } - return [] - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/pipes/renderRegionalFeatureSummary.pipe.ts b/src/atlasComponents/regionalFeatures/bsFeatures/pipes/renderRegionalFeatureSummary.pipe.ts deleted file mode 100644 index 829ed01981e3fb5a2061a058892f67742327dbb0..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/pipes/renderRegionalFeatureSummary.pipe.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { IBSSummaryResponse, TContextedFeature } from "../type"; -import { - IEEG_FEATURE_NAME -} from '../ieeg' -import { - RECEPTOR_FEATURE_NAME -} from '../receptor' -import { - EbrainsRegionalFeatureName -} from '../kgRegionalFeature' - -@Pipe({ - name: 'renderRegionalFeatureSummaryPipe', - pure: true, -}) - -export class RenderRegionalFeatureSummaryPipe implements PipeTransform{ - public transform(input: TContextedFeature<keyof IBSSummaryResponse>): string{ - if (input.featureName === IEEG_FEATURE_NAME) { - return input.result['name'] - } - if (input.featureName === RECEPTOR_FEATURE_NAME) { - return input.result['name'] - } - if (input.featureName === EbrainsRegionalFeatureName) { - return input.result['src_name'] - } - return `[Unknown feature type]` - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/ar/autoradiograph.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/ar/autoradiograph.component.ts deleted file mode 100644 index 0ea3e7fb5419c6db96466584773ba98e5d529934..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/ar/autoradiograph.component.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { Component, ElementRef, Input, OnChanges, ViewChild } from "@angular/core"; -import { BsFeatureReceptorBase } from "../base"; -import { CONST } from 'common/constants' -import { environment } from 'src/environments/environment' -import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; - -const { RECEPTOR_AR_CAPTION } = CONST - -export function isAr(filename: string, label: string = ''){ - return filename.indexOf(`_ar_${label}`) >= 0 -} - -@Component({ - selector: 'bs-features-receptor-autoradiograph', - templateUrl: './autoradiograph.template.html', - styleUrls: [ - './autoradiograph.style.css' - ] -}) - -export class BsFeatureReceptorAR extends BsFeatureReceptorBase implements OnChanges { - - public RECEPTOR_AR_CAPTION = RECEPTOR_AR_CAPTION - private DS_PREVIEW_URL = environment.DATASET_PREVIEW_URL - - @Input() - bsLabel: string - - @ViewChild('arContainer', { read: ElementRef }) - arContainer: ElementRef - - private renderBuffer: Uint8ClampedArray - private width: number - private height: number - private pleaseRender = false - - constructor(private worker: AtlasWorkerService){ - super() - } - async ngOnChanges(){ - this.error = null - this.urls = [] - if (!this.bsFeature) { - this.error = `bsFeature not populated` - return - } - if (!this.bsLabel) { - this.error = `bsLabel not populated` - return - } - - this.urls = this.bsFeature.__files - .filter(url => isAr(url, this.bsLabel)) - .map(url => { - return { url } - }) - try { - const { - "x-channel": channel, - "x-height": height, - "x-width": width, - content_type: contentType, - content_encoding: contentEncoding, - content, - } = this.bsFeature.__data.__autoradiographs[this.bsLabel] - - if (contentType !== "application/octet-stream") { - throw new Error(`contentType expected to be application/octet-stream, but is instead ${contentType}`) - } - if (contentEncoding !== "gzip; base64") { - throw new Error(`contentEncoding expected to be gzip; base64, but is ${contentEncoding} instead.`) - } - - const bin = atob(content) - const { pako } = (window as any).export_nehuba - const uint8array: Uint8Array = pako.inflate(bin) - - this.width = width - this.height = height - - const rgbaBuffer = await this.worker.sendMessage({ - method: "PROCESS_TYPED_ARRAY", - param: { - inputArray: uint8array, - width, - height, - channel - }, - transfers: [ uint8array.buffer ] - }) - - this.renderBuffer = rgbaBuffer.result.buffer - this.renderCanvas() - } catch (e) { - this.error = e.toString() - } - } - - private renderCanvas(){ - if (!this.arContainer) { - this.pleaseRender = true - return - } - - const arContainer = (this.arContainer.nativeElement as HTMLElement) - while (arContainer.firstChild) { - arContainer.removeChild(arContainer.firstChild) - } - - const canvas = document.createElement("canvas") - canvas.height = this.height - canvas.width = this.width - arContainer.appendChild(canvas) - const ctx = canvas.getContext("2d") - const imgData = ctx.createImageData(this.width, this.height) - imgData.data.set(this.renderBuffer) - ctx.putImageData(imgData, 0, 0) - } - - ngAfterViewChecked(){ - if (this.pleaseRender) this.renderCanvas() - } -} \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/ar/autoradiograph.style.css b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/ar/autoradiograph.style.css deleted file mode 100644 index c7ebabfec67ff4346377d1dfed4a4ce63a40c066..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/ar/autoradiograph.style.css +++ /dev/null @@ -1,5 +0,0 @@ -/* canvas created by createElement does not have encapsulation applied */ -.ar-container >>> canvas -{ - width: 100%; -} \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/ar/autoradiograph.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/ar/autoradiograph.template.html deleted file mode 100644 index ad467adc5a5f15bf37343c49a0c02fbb96a2c4d7..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/ar/autoradiograph.template.html +++ /dev/null @@ -1,21 +0,0 @@ -<ng-template [ngIf]="error"> - {{ error }} -</ng-template> - -<a *ngFor="let url of urls" - [href]="url.url" - class="no-hover" - download> - <i class="fas fa-download"></i> - <span> - {{ url.text || (url.url | getFilenamePipe) }} - </span> -</a> - -<figure> - <figcaption class="text-muted"> - Autoradiograph: {{ RECEPTOR_AR_CAPTION }} - </figcaption> - <div class="ar-container" #arContainer> - </div> -</figure> diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/base.ts b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/base.ts deleted file mode 100644 index b149e359d7dca68da294e98e9138a557bfedf41a..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/base.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Directive, Input } from "@angular/core"; -import { TBSDetail } from "./type"; - -@Directive() -export class BsFeatureReceptorBase { - @Input() - bsFeature: TBSDetail - - public urls: { - url: string - text?: string - }[] = [] - - public error = null - - // eslint-disable-next-line @typescript-eslint/no-empty-function - constructor(){} -} \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/entry/entry.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/entry/entry.component.ts deleted file mode 100644 index 7bcd410b784cdb61c221b969e4b3872074c63f3f..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/entry/entry.component.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { ChangeDetectorRef, Component, Inject, OnDestroy, Optional } from "@angular/core"; -import { BehaviorSubject, Observable, of, Subscription } from "rxjs"; -import { filter, map, shareReplay, startWith, switchMap, tap } from "rxjs/operators"; -import { BsRegionInputBase } from "../../bsRegionInputBase"; -import { REGISTERED_FEATURE_INJECT_DATA } from "../../constants"; -import { BsFeatureService, TFeatureCmpInput } from "../../service"; -import { TBSDetail } from "../type"; -import { ARIA_LABELS } from 'common/constants' - -@Component({ - selector: 'bs-features-receptor-entry', - templateUrl: './entry.template.html', - styleUrls: [ - './entry.style.css' - ], -}) - -export class BsFeatureReceptorEntry extends BsRegionInputBase implements OnDestroy{ - - private sub: Subscription[] = [] - public ARIA_LABELS = ARIA_LABELS - - private selectedREntryId$ = new BehaviorSubject<string>(null) - private _selectedREntryId: string - set selectedREntryId(id: string){ - this.selectedREntryId$.next(id) - this._selectedREntryId = id - } - get selectedREntryId(){ - return this._selectedREntryId - } - - public selectedReceptor$: Observable<TBSDetail> = this.selectedREntryId$.pipe( - switchMap(id => id - ? this.getFeatureInstance('ReceptorDistribution', id) - : of(null) - ), - shareReplay(1), - ) - - public hasPrAr$: Observable<boolean> = this.selectedReceptor$.pipe( - map(detail => !!detail.__data.__profiles && Object.keys(detail.__data.__profiles).length > 0), - ) - - ngOnDestroy(){ - while (this.sub.length > 0) this.sub.pop().unsubscribe() - } - - public receptorsSummary$ = this.region$.pipe( - filter(v => !!v), - switchMap(() => this.getFeatureInstancesList('ReceptorDistribution')), - startWith([]), - map( - arr => this.data?.featureId - ? arr.filter(it => it['@id'] === this.data.featureId) - : arr - ), - shareReplay(1), - ) - - public onSelectReceptor(receptor: string){ - this.selectedReceptor = receptor - } - - public selectedReceptor = null - public allReceptors$ = this.selectedReceptor$.pipe( - map(rec => { - if (!rec) return [] - return Object.keys(rec.__receptor_symbols || {}) - }) - ) - - constructor( - svc: BsFeatureService, - cdr: ChangeDetectorRef, - @Optional() @Inject(REGISTERED_FEATURE_INJECT_DATA) private data: TFeatureCmpInput - ){ - super(svc, data) - this.sub.push( - this.selectedReceptor$.subscribe(() => { - cdr.markForCheck() - }), - this.receptorsSummary$.subscribe(arr => { - if (arr && arr.length > 0) { - this.selectedREntryId = arr[0]['@id'] - } else { - this.selectedREntryId = null - } - }) - ) - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/entry/entry.style.css b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/entry/entry.style.css deleted file mode 100644 index 71beb4683eec695ddf613bfcf2eecac35e79773d..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/entry/entry.style.css +++ /dev/null @@ -1,8 +0,0 @@ -:host -{ - display: block; - width: 100%; - height: 100%; - padding-left:24px; - padding-right:24px; -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/entry/entry.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/entry/entry.template.html deleted file mode 100644 index 48a24c834f0ef7e943434a6242c12fe49b0f5b91..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/entry/entry.template.html +++ /dev/null @@ -1,50 +0,0 @@ -<mat-divider class="mt-2 mb-2"></mat-divider> - -<!-- potential selector for receptor data --> -<mat-select class="invisible" [(value)]="selectedREntryId"> - <mat-option *ngFor="let rec of receptorsSummary$ | async" - [value]="rec['@id']"> - {{ rec.name }} - </mat-option> -</mat-select> - -<ng-template [ngIf]="!(selectedReceptor$ | async)"> - <spinner-cmp></spinner-cmp> -</ng-template> - -<ng-template let-selectedRec [ngIf]="selectedReceptor$ | async"> - <bs-features-receptor-fingerprint - (onSelectReceptor)="onSelectReceptor($event)" - [bsFeature]="selectedRec"> - </bs-features-receptor-fingerprint> - - <ng-template [ngIf]="hasPrAr$ | async"> - <mat-divider></mat-divider> - - <mat-form-field class="mt-2 w-100"> - <mat-label> - Select a receptor - </mat-label> - <mat-select [(value)]="selectedReceptor"> - <mat-option - *ngFor="let receptorName of (allReceptors$ | async)" - [value]="receptorName"> - {{ receptorName }} - </mat-option> - </mat-select> - </mat-form-field> - - <bs-features-receptor-profile - *ngIf="selectedReceptor" - [bsFeature]="selectedRec" - [bsLabel]="selectedReceptor"> - </bs-features-receptor-profile> - - <bs-features-receptor-autoradiograph - *ngIf="selectedReceptor" - [bsFeature]="selectedRec" - [bsLabel]="selectedReceptor"> - </bs-features-receptor-autoradiograph> - </ng-template> - -</ng-template> diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/fp/fp.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/fp/fp.component.ts deleted file mode 100644 index a061221118bf8420bbf6883b14c15f18680c4fb8..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/fp/fp.component.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, HostListener, Inject, OnChanges, OnDestroy, OnInit, Optional, Output } from "@angular/core"; -import { fromEvent, Observable, Subscription } from "rxjs"; -import { distinctUntilChanged, map } from "rxjs/operators"; -import { BS_DARKTHEME } from "../../constants"; -import { BsFeatureReceptorBase } from "../base"; -import { CONST } from 'common/constants' - -const { RECEPTOR_FP_CAPTION } = CONST - -@Component({ - selector: 'bs-features-receptor-fingerprint', - templateUrl: './fp.template.html', - styleUrls: [ - './fp.style.css' - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class BsFeatureReceptorFingerprint extends BsFeatureReceptorBase implements OnChanges, OnInit, OnDestroy{ - - public RECEPTOR_FP_CAPTION = RECEPTOR_FP_CAPTION - private sub: Subscription[] = [] - - @HostListener('click') - onClick(){ - if (this.selectedReceptor) { - this.onSelectReceptor.emit(this.selectedReceptor) - } - } - - @Output() - public onSelectReceptor = new EventEmitter() - private selectedReceptor: any - - constructor( - private elRef: ElementRef, - @Optional() @Inject(BS_DARKTHEME) public darktheme$: Observable<boolean>, - ){ - super() - } - - ngOnInit(){ - // without, when devtool is out, runs sluggishly - // informing angular that change occurs here will be handled by programmer, and not angular - - this.sub.push( - fromEvent<CustomEvent>(this.elRef.nativeElement, 'kg-ds-prv-regional-feature-mouseover').pipe( - map(ev => ev.detail?.data?.receptor?.label), - distinctUntilChanged(), - ).subscribe(label => { - this.selectedReceptor = label - }) - ) - } - - ngOnDestroy() { - while (this.sub.length > 0) this.sub.pop().unsubscribe() - } - - ngOnChanges(){ - this.error = null - this.urls = [] - - if (!this.bsFeature) { - this.error = `bsFeature is not populated` - return - } - - this.urls.push( - ...this.bsFeature.__files - .filter(u => /_fp_/.test(u)) - .map(url => { - return { - url, - } - }), - ...this.bsFeature.__files - .filter(u => !/_pr_|_ar_/.test(u) && /receptors\.tsv$/.test(u)) - .map(url => { - return { - url, - } - }) - ) - - const radarEl = (this.elRef.nativeElement as HTMLElement).querySelector<any>('kg-dataset-dumb-radar') - radarEl.radarBs = this.bsFeature.__data.__fingerprint - radarEl.metaBs = this.bsFeature.__receptor_symbols - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/fp/fp.style.css b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/fp/fp.style.css deleted file mode 100644 index e2a98b2709c53c5853eebdbfc84370f371d84868..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/fp/fp.style.css +++ /dev/null @@ -1,18 +0,0 @@ -kg-dataset-dumb-radar -{ - display: block; - min-height: 20em; -} - -/* figure -{ - width: 100%; - height: 100%; -} - -kg-dataset-dumb-radar -{ - display: block; - width: 100%; - height: 100%; -} */ \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/fp/fp.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/fp/fp.template.html deleted file mode 100644 index 50d505459bb9893b0d7b98ce20f596ba2ba241a9..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/fp/fp.template.html +++ /dev/null @@ -1,22 +0,0 @@ -<ng-template [ngIf]="error"> - {{ error }} -</ng-template> - -<a *ngFor="let url of urls" - [href]="url.url" - class="no-hover" - download> - <i class="fas fa-download"></i> - <span> - {{ url.text || (url.url | getFilenamePipe) }} - </span> -</a> - -<figure> - <figcaption class="text-muted"> - Fingerprint : {{ RECEPTOR_FP_CAPTION }} - </figcaption> - <kg-dataset-dumb-radar - [attr.kg-ds-prv-darkmode]="darktheme$ && (darktheme$ | async)"> - </kg-dataset-dumb-radar> -</figure> diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/hasReceptor.directive.ts b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/hasReceptor.directive.ts deleted file mode 100644 index 6ef215934ac65dda3977d9e938d328ca45956f22..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/hasReceptor.directive.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Directive, EventEmitter, Inject, OnDestroy, Optional, Output } from "@angular/core"; -import { merge, Observable, of, Subscription } from "rxjs"; -import { catchError, map, mapTo, switchMap } from "rxjs/operators"; -import { BsRegionInputBase } from "../bsRegionInputBase"; -import { REGISTERED_FEATURE_INJECT_DATA } from "../constants"; -import { BsFeatureService, TFeatureCmpInput } from "../service"; -import { IBSSummaryResponse, IRegionalFeatureReadyDirective } from "../type"; - -@Directive({ - selector: '[bs-features-receptor-directive]', - exportAs: 'bsFeatureReceptorDirective' -}) - -export class BsFeatureReceptorDirective extends BsRegionInputBase implements IRegionalFeatureReadyDirective, OnDestroy { - - private sub: Subscription[] = [] - - ngOnDestroy(){ - while (this.sub.length > 0) this.sub.pop().unsubscribe() - } - public results$: Observable<IBSSummaryResponse['ReceptorDistribution'][]> = this.region$.pipe( - switchMap(() => merge( - of([]), - this.getFeatureInstancesList('ReceptorDistribution').pipe( - catchError(() => of([])) - ) - )), - ) - - public hasReceptor$ = this.results$.pipe( - map(arr => arr.length > 0) - ) - - public busy$ = this.region$.pipe( - switchMap(() => merge( - of(true), - this.results$.pipe( - mapTo(false) - ) - )) - ) - - constructor( - svc: BsFeatureService, - @Optional() @Inject(REGISTERED_FEATURE_INJECT_DATA) data: TFeatureCmpInput, - ){ - super(svc, data) - this.sub.push( - this.busy$.subscribe(flag => this.fetchingFlagEmitter.emit(flag)) - ) - } - - @Output('bs-features-receptor-directive-fetching-flag') - public fetchingFlagEmitter = new EventEmitter<boolean>() -} \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/index.ts b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/index.ts deleted file mode 100644 index 79f8e55d97df512175ef1be0f913512bff41a16f..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { BSFeatureReceptorModule } from './module' -export { RECEPTOR_FEATURE_NAME } from './type' diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/module.ts b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/module.ts deleted file mode 100644 index 94f11b9df04e239090ed67a1bca192d2bd924b9c..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/module.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; -import { FormsModule } from "@angular/forms"; -import { AngularMaterialModule } from "src/sharedModules"; -import { UtilModule } from "src/util"; -import { BsFeatureService } from "../service"; -import { BsFeatureReceptorAR } from "./ar/autoradiograph.component"; -import { BsFeatureReceptorEntry } from "./entry/entry.component"; -import { BsFeatureReceptorFingerprint } from "./fp/fp.component"; -import { BsFeatureReceptorDirective } from "./hasReceptor.directive"; -import { BsFeatureReceptorProfile } from "./profile/profile.component"; -import { RECEPTOR_FEATURE_NAME } from "./type"; - -@NgModule({ - imports: [ - CommonModule, - UtilModule, - AngularMaterialModule, - FormsModule, - ], - declarations: [ - BsFeatureReceptorProfile, - BsFeatureReceptorAR, - BsFeatureReceptorFingerprint, - BsFeatureReceptorEntry, - BsFeatureReceptorDirective, - ], - exports: [ - BsFeatureReceptorEntry, - BsFeatureReceptorDirective, - ], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA - ] -}) - -export class BSFeatureReceptorModule{ - constructor(svc: BsFeatureService){ - svc.registerFeature({ - name: RECEPTOR_FEATURE_NAME, - icon: 'fas fa-info', - View: BsFeatureReceptorEntry, - Ctrl: BsFeatureReceptorDirective, - }) - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/profile/profile.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/profile/profile.component.ts deleted file mode 100644 index 6b33a73b4810c5828752a783b006b0dbb6928b94..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/profile/profile.component.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { ChangeDetectionStrategy, Component, ElementRef, Inject, Input, OnChanges, Optional } from "@angular/core"; -import { Observable } from "rxjs"; -import { BS_DARKTHEME } from "../../constants"; -import { BsFeatureReceptorBase } from "../base"; -import { CONST } from 'common/constants' - -export function isPr(filename: string, label: string = ''){ - return filename.indexOf(`_pr_${label}`) >= 0 -} - -const { RECEPTOR_PR_CAPTION } = CONST - -@Component({ - selector: 'bs-features-receptor-profile', - templateUrl: './profile.template.html', - styleUrls: [ - './profile.style.css' - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class BsFeatureReceptorProfile extends BsFeatureReceptorBase implements OnChanges{ - - public RECEPTOR_PR_CAPTION = RECEPTOR_PR_CAPTION - - @Input() - bsLabel: string - - constructor( - private elRef: ElementRef, - @Optional() @Inject(BS_DARKTHEME) public darktheme$: Observable<boolean>, - ){ - super() - } - - ngOnChanges(){ - this.error = null - this.urls = [] - - if (!this.bsFeature) { - this.error = `bsFeature not populated` - return - } - if (!this.bsLabel) { - this.error = `bsLabel not populated` - return - } - - this.urls = this.bsFeature.__files - .filter(url => isPr(url, this.bsLabel)) - .map(url => { - return { url } - }) - - const profileBs = this.bsFeature.__data.__profiles[this.bsLabel] - const lineEl = (this.elRef.nativeElement as HTMLElement).querySelector<any>('kg-dataset-dumb-line') - lineEl.profileBs = profileBs - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/profile/profile.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/profile/profile.template.html deleted file mode 100644 index 0b71d2ac46cb92527b2a44af80a94a69961568b5..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/profile/profile.template.html +++ /dev/null @@ -1,22 +0,0 @@ -<ng-template [ngIf]="error"> - {{ error }} -</ng-template> - -<a *ngFor="let url of urls" - [href]="url.url" - class="no-hover" - download> - <i class="fas fa-download"></i> - <span> - {{ url.text || (url.url | getFilenamePipe) }} - </span> -</a> - -<figure> - <figcaption class="text-muted"> - Profile: {{ RECEPTOR_PR_CAPTION }} - </figcaption> - <kg-dataset-dumb-line - [attr.kg-ds-prv-darkmode]="darktheme$ && (darktheme$ | async)"> - </kg-dataset-dumb-line> -</figure> \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/type.ts b/src/atlasComponents/regionalFeatures/bsFeatures/receptor/type.ts deleted file mode 100644 index 9ef1efe8b17727ded7f382784c035d780d323fc6..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/type.ts +++ /dev/null @@ -1,63 +0,0 @@ -type TReceptorCommon = { - latex: string - markdown: string - name: string -} - -type TReceptor = string // TODO complete all possible neuroreceptor - -type TReceptorSymbol = { - [key: string]: { - receptor: TReceptorCommon - neurotransmitter: TReceptorCommon & { label: string } - } -} - -type TProfile = { - [key: number]: number -} - -type TBSFingerprint = { - unit: string - labels: TReceptor[] - meanvals: number[] - stdvals: number[] - n: 1 -} - -export type TBSSummary = { - ['@id']: string - name: string - info: string - origin_datainfos?: ({ - name: string - description: string - urls: { - doi: string - cite?: string - }[] - })[] -} - -export type TBSDetail = TBSSummary & { - __files: string[] - __receptor_symbols: TReceptorSymbol - __data: { - __profiles: { - [key: string]: TProfile - } - __autoradiographs: { - [key: string]: { - content_type: string - content_encoding: string - ['x-width']: number - ['x-height']: number - ['x-channel']: number - content: string - } - } - __fingerprint: TBSFingerprint - } -} - -export const RECEPTOR_FEATURE_NAME = 'receptor density' diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/regionalFeatureWrapper/regionalFeatureWrapper.component.ts b/src/atlasComponents/regionalFeatures/bsFeatures/regionalFeatureWrapper/regionalFeatureWrapper.component.ts deleted file mode 100644 index 76a2ad1f2f9bdffbbb598992d542989a48127279..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/regionalFeatureWrapper/regionalFeatureWrapper.component.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { Component, ComponentFactory, ComponentFactoryResolver, Inject, Injector, Input, OnChanges, OnDestroy, Optional, ViewChild, ViewContainerRef } from "@angular/core"; -import { IBSSummaryResponse, TContextedFeature, TRegion } from "../type"; -import { BsFeatureService, TFeatureCmpInput } from "../service"; -import { combineLatest, Observable, Subject } from "rxjs"; -import { debounceTime, map, shareReplay, startWith } from "rxjs/operators"; -import { REGISTERED_FEATURE_INJECT_DATA } from "../constants"; -import { ARIA_LABELS } from 'common/constants' -import { - IEEG_FEATURE_NAME -} from '../ieeg' -import { - RECEPTOR_FEATURE_NAME -} from '../receptor' -import { - EbrainsRegionalFeatureName -} from '../kgRegionalFeature' -import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, TOverwriteShowDatasetDialog } from "src/util/interfaces"; - -@Component({ - selector: 'regional-feature-wrapper', - templateUrl: './regionalFeatureWrapper.template.html', - styleUrls: [ - './regionalFeatureWrapper.style.css' - ] -}) - -export class RegionalFeatureWrapperCmp implements OnChanges, OnDestroy{ - - public useVirtualScroll = false - - public ARIA_LABELS = ARIA_LABELS - - @Input() - region: TRegion - - @ViewChild('regionalFeatureContainerTmpl', { read: ViewContainerRef }) - regionalFeatureContainerRef: ViewContainerRef - - private weakmap = new WeakMap<(new () => any), ComponentFactory<any>>() - - private ondestroyCb: (() => void)[] = [] - constructor( - private svc: BsFeatureService, - private cfr: ComponentFactoryResolver, - @Optional() @Inject(OVERWRITE_SHOW_DATASET_DIALOG_TOKEN) private overwriteFn: TOverwriteShowDatasetDialog - ){ - const sub = this.registeredFeatures$.subscribe(arr => this.registeredFeatures = arr) - this.ondestroyCb.push(() => sub.unsubscribe()) - } - - private regionOnDestroyCb: (() => void)[] = [] - private setupRegionalFeatureCtrl(){ - if (!this.region) return - const { region } = this - for (const feat of this.svc.registeredFeatures){ - const { name, icon } = feat - const ctrl = new feat.Ctrl(this.svc, { region }) - const sub = combineLatest([ - ctrl.busy$, - ctrl.results$.pipe( - startWith([]) - ) - ]).subscribe( - ([busy, results]) => { - this.registeredFeatureRawRegister[name] = { busy, results, icon } - this.registeredFeatureFireStream$.next(true) - } - ) - this.regionOnDestroyCb.push(() => sub.unsubscribe()) - } - } - private cleanUpRegionalFeature(){ - while (this.regionOnDestroyCb.length) this.regionOnDestroyCb.pop()() - /** - * emit null to signify flush out of existing scan map - */ - this.registeredFeatureRawRegister = {} - this.registeredFeatureFireStream$.next(true) - } - - private registeredFeatureRawRegister: { - [key: string]: { - icon: string - busy: boolean - results: any[] - } - } = {} - private registeredFeatureFireStream$ = new Subject() - private registeredFeatureMasterStream$ = this.registeredFeatureFireStream$.pipe( - debounceTime(16), - /** - * must not use mapTo operator - * otherwise the emitted value will not change - */ - map(() => this.registeredFeatureRawRegister), - shareReplay(1), - ) - public busy$: Observable<boolean> = this.registeredFeatureMasterStream$.pipe( - map(obj => { - for (const key in obj) { - if(obj[key].busy) return true - } - return false - }), - ) - - public registeredFeatures: TContextedFeature<keyof IBSSummaryResponse>[] = [] - private registeredFeatures$: Observable<TContextedFeature<keyof IBSSummaryResponse>[]> = this.registeredFeatureMasterStream$.pipe( - map(obj => { - const returnArr = [] - for (const name in obj) { - if (obj[name].busy || obj[name].results.length === 0) { - continue - } - for (const result of obj[name].results) { - const objToBeInserted = { - featureName: name, - icon: obj[name].icon, - result - } - /** - * place ebrains regional features at the end - */ - if (name === EbrainsRegionalFeatureName) { - returnArr.push(objToBeInserted) - } else { - returnArr.unshift(objToBeInserted) - } - } - } - - return returnArr - }), - ) - - ngOnChanges(){ - this.cleanUpRegionalFeature() - this.setupRegionalFeatureCtrl() - } - - ngOnDestroy(){ - this.cleanUpRegionalFeature() - while(this.ondestroyCb.length) this.ondestroyCb.pop()() - } - - public handleFeatureClick(contextedFeature: TContextedFeature<any>){ - if (!this.overwriteFn) { - console.warn(`show dialog function not overwritten!`) - return - } - - const featureId = contextedFeature.result['@id'] - const arg = {} - if (contextedFeature.featureName === RECEPTOR_FEATURE_NAME) { - arg['name'] = contextedFeature.result['name'] - arg['description'] = contextedFeature.result['info'] - arg['urls'] = [] - for (const info of contextedFeature.result['origin_datainfos']) { - arg['urls'].push(...info.urls) - } - } - - if (contextedFeature.featureName === IEEG_FEATURE_NAME) { - arg['name'] = contextedFeature.result['name'] - arg['description'] = contextedFeature.result['description'] || ' ' - arg['isGdprProtected'] = true - arg['urls'] = [] - for (const info of contextedFeature.result['origin_datainfos']) { - arg['urls'].push(...(info.urls || [])) - } - } - - if (contextedFeature.featureName === EbrainsRegionalFeatureName) { - arg['summary'] = contextedFeature.result - } - - const { region } = this - - const feat = this.svc.registeredFeatures.find(f => f.name === contextedFeature.featureName) - if (!feat) { - console.log(`cannot find feature with name ${contextedFeature.featureName}`) - return - } - - const cf = (() => { - if (!feat.View) return null - const mapped = this.weakmap.get(feat.View) - if (mapped) return mapped - const _cf = this.cfr.resolveComponentFactory(feat.View) - this.weakmap.set(feat.View ,_cf) - return _cf - })() - - this.overwriteFn({ - region, - dataType: contextedFeature.featureName, - view: (() => { - if (!cf) return null - const injector = Injector.create({ - providers: [{ - provide: REGISTERED_FEATURE_INJECT_DATA, - useValue: { region, featureId } as TFeatureCmpInput - }], - }) - const cmp = cf.create(injector) - return cmp.hostView - })(), - ...arg, - }) - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/regionalFeatureWrapper/regionalFeatureWrapper.style.css b/src/atlasComponents/regionalFeatures/bsFeatures/regionalFeatureWrapper/regionalFeatureWrapper.style.css deleted file mode 100644 index 816e7ba25d2192ebd17900e60655db92bf17063b..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/regionalFeatureWrapper/regionalFeatureWrapper.style.css +++ /dev/null @@ -1,19 +0,0 @@ -.button-text -{ - white-space: normal; - line-height: 1.5rem; - text-align: center; -} - -.feature-container, -cdk-virtual-scroll-viewport -{ - min-height: 24rem; -} - - -.feature-container -{ - height: 24rem; - overflow-y: scroll; -} \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/regionalFeatureWrapper/regionalFeatureWrapper.template.html b/src/atlasComponents/regionalFeatures/bsFeatures/regionalFeatureWrapper/regionalFeatureWrapper.template.html deleted file mode 100644 index 020145c3c36c406a74c3575422068d0b43700892..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/regionalFeatureWrapper/regionalFeatureWrapper.template.html +++ /dev/null @@ -1,85 +0,0 @@ -<ng-template [ngTemplateOutlet]="resultTmpl"> -</ng-template> - -<ng-template #busyTmpl> - <spinner-cmp></spinner-cmp> -</ng-template> - -<ng-template #resultTmpl> - - <!-- virtual scroll. do not activate until autosize is supported --> - <cdk-virtual-scroll-viewport - *ngIf="useVirtualScroll; else regularScrollTmpl" - [attr.aria-label]="ARIA_LABELS.LIST_OF_DATASETS_ARIA_LABEL" - class="h-100" - minBufferPx="200" - maxBufferPx="400" - itemSize="50"> - <div *cdkVirtualFor="let feature of registeredFeatures; templateCacheSize: 20; let index = index" - class="h-50px overflow-hidden"> - - <!-- divider, show if not first --> - <mat-divider *ngIf="index !== 0"></mat-divider> - <ng-container *ngTemplateOutlet="itemContainer; context: { $implicit: feature }"> - </ng-container> - - </div> - </cdk-virtual-scroll-viewport> - - <!-- fallback, regular scroll --> - <!-- less efficient on large list, but for now should do --> - <ng-template #regularScrollTmpl> - <div class="feature-container" - [attr.aria-label]="ARIA_LABELS.LIST_OF_DATASETS_ARIA_LABEL"> - - <!-- if busy, show spinner --> - <ng-template [ngIf]="busy$ | async" [ngIfElse]="notBusyTmpl"> - <ng-template [ngTemplateOutlet]="busyTmpl"></ng-template> - </ng-template> - - <ng-template #notBusyTmpl> - <ng-template [ngIf]="registeredFeatures.length === 0"> - <span class="text-muted"> - No regional features found. - </span> - </ng-template> - </ng-template> - <div *ngFor="let feature of registeredFeatures; let index = index" - class="overflow-hidden"> - - <!-- divider, show if not first --> - <mat-divider *ngIf="index !== 0"></mat-divider> - <ng-container *ngTemplateOutlet="itemContainer; context: { $implicit: feature }"> - </ng-container> - - </div> - </div> - </ng-template> - -</ng-template> - -<!-- feature template --> -<ng-template #itemContainer let-feature> - <div class="d-block pt-4 cursor-default" - (click)="handleFeatureClick(feature)" - mat-ripple> - - <!-- mat-chip container --> - <!-- do not use mat-chip-list to avoid adding incorrect a11y info --> - <div class="transform-origin-left-center scale-80"> - <mat-chip *ngFor="let badge of feature | getBadgeFromFeaturePipe" - [color]="badge.color" - selected> - {{ badge.text }} - </mat-chip> - </div> - - <small> - {{ feature | renderRegionalFeatureSummaryPipe }} - </small> - </div> -</ng-template> - -<!-- dummy container --> -<ng-template #regionalFeatureContainerTmpl> -</ng-template> diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/service.ts b/src/atlasComponents/regionalFeatures/bsFeatures/service.ts deleted file mode 100644 index 7f52201e0619227b8ba17f23d0b4ce6cb710ce23..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/service.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { HttpClient } from "@angular/common/http"; -import { Inject, Injectable } from "@angular/core"; -import { BehaviorSubject } from "rxjs"; -import { shareReplay } from "rxjs/operators"; -import { CachedFunction } from "src/util/fn"; -import { BS_ENDPOINT } from "./constants"; -import { IBSSummaryResponse, IBSDetailResponse, TRegion, IFeatureList, IRegionalFeatureReadyDirective } from './type' -import { SIIBRA_FEATURE_KEY as IEEG_FEATURE_KEY } from '../bsFeatures/ieeg/type' - -function processRegion(region: TRegion) { - return `${region.name} ${region.status ? region.status : '' }` -} - -export type TFeatureCmpInput = { - region: TRegion - featureId?: string -} - -export type TRegisteredFeature<V = any> = { - name: string - icon: string // fontawesome font class, e.g. `fas fa-link-alt` - View: new (...arg: any[]) => V - Ctrl: new (svc: BsFeatureService, data: TFeatureCmpInput) => IRegionalFeatureReadyDirective -} - -@Injectable({ - providedIn: 'root' -}) -export class BsFeatureService{ - - static SpaceFeatureSet = new Set([ - IEEG_FEATURE_KEY - ]) - - public registeredFeatures: TRegisteredFeature[] = [] - public registeredFeatures$ = new BehaviorSubject<TRegisteredFeature[]>(this.registeredFeatures) - public getAllFeatures$ = this.http.get(`${this.bsEndpoint}/features`).pipe( - shareReplay(1) - ) - - public listFeatures(region: TRegion){ - const { context } = region - const { atlas, parcellation } = context - return this.http.get<IFeatureList>( - `${this.bsEndpoint}/atlases/${encodeURIComponent(atlas["@id"])}/parcellations/${encodeURIComponent(parcellation['@id'])}/regions/${encodeURIComponent(processRegion(region))}/features` - ) - } - - private getUrl(arg: { - atlasId: string - parcId: string - spaceId: string - region: TRegion - featureName: string - featureId?: string - }){ - const { - atlasId, - parcId, - spaceId, - region, - featureName, - featureId, - } = arg - - if (BsFeatureService.SpaceFeatureSet.has(featureName)) { - - const url = new URL(`${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/spaces/${encodeURIComponent(spaceId)}/features/${encodeURIComponent(featureName)}${ featureId ? ('/' + encodeURIComponent(featureId)) : '' }`) - url.searchParams.set('parcellation_id', parcId) - url.searchParams.set('region', processRegion(region)) - - return url.toString() - } - - if (!featureId) { - return `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations/${encodeURIComponent(parcId)}/regions/${encodeURIComponent(processRegion(region))}/features/${encodeURIComponent(featureName)}` - } - return `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations/${encodeURIComponent(parcId)}/regions/${encodeURIComponent(processRegion(region))}/features/${encodeURIComponent(featureName)}/${encodeURIComponent(featureId)}` - } - - @CachedFunction({ - serialization: (featureName, region) => `${featureName}::${processRegion(region)}` - }) - public getFeatures<T extends keyof IBSSummaryResponse>(featureName: T, region: TRegion){ - const { context } = region - const { atlas, parcellation, template } = context - const url = this.getUrl({ - atlasId: atlas['@id'], - parcId: parcellation['@id'], - region, - featureName, - spaceId: template['@id'] - }) - - return this.http.get<IBSSummaryResponse[T][]>( - url - ).pipe( - shareReplay(1) - ) - } - - @CachedFunction({ - serialization: (featureName, region, featureId) => `${featureName}::${processRegion(region)}::${featureId}` - }) - public getFeature<T extends keyof IBSDetailResponse>(featureName: T, region: TRegion, featureId: string) { - const { context } = region - const { atlas, parcellation, template } = context - const url = this.getUrl({ - atlasId: atlas['@id'], - parcId: parcellation['@id'], - spaceId: template['@id'], - region, - featureName, - featureId - }) - return this.http.get<IBSSummaryResponse[T]&IBSDetailResponse[T]>(url).pipe( - shareReplay(1) - ) - } - - public registerFeature(feature: TRegisteredFeature){ - if (this.registeredFeatures.find(v => v.name === feature.name)) { - throw new Error(`feature ${feature.name} already registered`) - } - this.registeredFeatures.push(feature) - this.registeredFeatures$.next(this.registeredFeatures) - } - - public deregisterFeature(name: string){ - this.registeredFeatures = this.registeredFeatures.filter(v => v.name !== name) - this.registeredFeatures$.next(this.registeredFeatures) - } - - constructor( - private http: HttpClient, - @Inject(BS_ENDPOINT) private bsEndpoint: string, - ){ - - } -} diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/type.ts b/src/atlasComponents/regionalFeatures/bsFeatures/type.ts deleted file mode 100644 index 84731e48984af2dd020be92f5d06e3b91429f558..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/bsFeatures/type.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { IHasId } from "src/util/interfaces"; -import { TBSDetail as TReceptorDetail, TBSSummary as TReceptorSummary } from "./receptor/type"; -import { KG_REGIONAL_FEATURE_KEY, TBSDetail as TKGDetail, TBSSummary as TKGSummary } from './kgRegionalFeature/type' -import { SIIBRA_FEATURE_KEY, TBSSummary as TIEEGSummary, TBSIeegSessionDetail as TIEEGDetail } from './ieeg/type' -import { Observable } from "rxjs"; -import { InjectionToken } from "@angular/core"; - -/** - * change KgRegionalFeature -> EbrainsRegionalDataset in prod - */ - -export interface IBSSummaryResponse { - 'ReceptorDistribution': TReceptorSummary - [KG_REGIONAL_FEATURE_KEY]: TKGSummary - [SIIBRA_FEATURE_KEY]: TIEEGSummary -} - -export interface IBSDetailResponse { - 'ReceptorDistribution': TReceptorDetail - [KG_REGIONAL_FEATURE_KEY]: TKGDetail - [SIIBRA_FEATURE_KEY]: TIEEGDetail -} - -export type TRegion = { - name: string - status?: string - context: { - atlas: IHasId - template: IHasId - parcellation: IHasId - } -} - -export interface IFeatureList { - features: { - [key: string]: string - }[] -} - -export interface IRegionalFeatureReadyDirective { - ngOnDestroy(): void - busy$: Observable<boolean> - results$: Observable<IBSSummaryResponse[keyof IBSSummaryResponse][]> -} - -export type TContextedFeature<T extends keyof IBSSummaryResponse> = { - featureName: string - icon: string - result: IBSSummaryResponse[T] -} - -export const GENERIC_INFO_INJ_TOKEN = new InjectionToken('GENERIC_INFO_INJ_TOKEN') diff --git a/src/atlasComponents/regionalFeatures/featureContainer/featureContainer.component.spec.ts b/src/atlasComponents/regionalFeatures/featureContainer/featureContainer.component.spec.ts deleted file mode 100644 index 6821e237ff0aae95e6eaa42c99989dec7d5e9b65..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/featureContainer/featureContainer.component.spec.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { CommonModule } from "@angular/common" -import { ChangeDetectorRef, Component, ComponentRef, EventEmitter, NgModule } from "@angular/core" -import { async, TestBed } from "@angular/core/testing" -import { By } from "@angular/platform-browser" -import { RegionalFeaturesService } from "../regionalFeature.service" -import { ISingleFeature } from "../singleFeatures/interfaces" -import { FeatureContainer } from "./featureContainer.component" - -const dummyCmpType = 'dummyType' - -@Component({ - template: `{{ text }}` -}) - -class DummyComponent implements ISingleFeature{ - text = 'hello world' - feature: any - region: any - viewChanged = new EventEmitter<boolean>() -} - -@Component({ - template: '' -}) - -class HostCmp{ - public feature: any - public region: any - - constructor(public cdr: ChangeDetectorRef){ - - } - - detectChange(){ - this.cdr.detectChanges() - } -} - -const serviceStub = { - mapFeatToCmp: new Map([ - [dummyCmpType, DummyComponent] - ]) -} - -describe('> featureContainer.component.ts', () => { - describe('> FeatureContainer', () => { - - beforeEach(async () => { - - await TestBed.configureTestingModule({ - imports: [ - CommonModule, - ], - declarations: [ - FeatureContainer, - DummyComponent, - HostCmp, - ], - providers: [ - { - provide: RegionalFeaturesService, - useValue: serviceStub - } - ] - }).overrideComponent(HostCmp, { - set: { - template: ` - <feature-container - [feature]="feature" - [region]="region" - (viewChanged)="detectChange()"> - </feature-container>` - } - }).compileComponents() - - }) - - it('> can be created', () => { - const fixture = TestBed.createComponent(HostCmp) - expect(fixture).toBeTruthy() - const featContainer = fixture.debugElement.query(By.directive(FeatureContainer)) - expect(featContainer).toBeTruthy() - }) - - describe('> if inputs change', () => { - it('> if input changed, but feature is not one of them, map.get will not be called', () => { - const fixture = TestBed.createComponent(HostCmp) - // const featContainer = fixture.debugElement.query(By.directive(FeatureContainer)) - spyOn(serviceStub.mapFeatToCmp, 'get').and.callThrough() - fixture.componentInstance.region = { - name: 'tesla' - } - fixture.detectChanges() - expect(serviceStub.mapFeatToCmp.get).not.toHaveBeenCalled() - }) - - it('> if input changed, feature is one of them, will not call map.get', () => { - const fixture = TestBed.createComponent(HostCmp) - const dummyFeature = { - type: dummyCmpType - } - spyOn(serviceStub.mapFeatToCmp, 'get').and.callThrough() - fixture.componentInstance.feature = dummyFeature - fixture.detectChanges() - expect(serviceStub.mapFeatToCmp.get).toHaveBeenCalledWith(dummyCmpType) - }) - - it('> should render default txt', () => { - const fixture = TestBed.createComponent(HostCmp) - const dummyFeature = { - type: dummyCmpType - } - fixture.componentInstance.feature = dummyFeature - fixture.detectChanges() - const text = fixture.nativeElement.textContent - expect(text).toContain('hello world') - }) - - it('> if inner component changes, if view changed does not emit, will not change ui', () => { - - const fixture = TestBed.createComponent(HostCmp) - const dummyFeature = { - type: dummyCmpType - } - fixture.componentInstance.feature = dummyFeature - fixture.detectChanges() - const featureContainer = fixture.debugElement.query( - By.directive(FeatureContainer) - ) - const cr = (featureContainer.componentInstance as FeatureContainer)['cr'] as ComponentRef<DummyComponent> - cr.instance.text = 'foo bar' - const text = fixture.nativeElement.textContent - expect(text).toContain('hello world') - }) - - it('> if inner component changes, and viewChanged is emitted, ui should change accordingly', () => { - - const fixture = TestBed.createComponent(HostCmp) - const dummyFeature = { - type: dummyCmpType - } - fixture.componentInstance.feature = dummyFeature - fixture.detectChanges() - const featureContainer = fixture.debugElement.query( - By.directive(FeatureContainer) - ) - const cr = (featureContainer.componentInstance as FeatureContainer)['cr'] as ComponentRef<DummyComponent> - cr.instance.text = 'foo bar' - cr.instance.viewChanged.emit(true) - const text = fixture.nativeElement.textContent - expect(text).toContain('foo bar') - }) - }) - }) -}) diff --git a/src/atlasComponents/regionalFeatures/featureContainer/featureContainer.component.ts b/src/atlasComponents/regionalFeatures/featureContainer/featureContainer.component.ts deleted file mode 100644 index 2b2391599494ac22cadc250fa479255fa39d2179..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/featureContainer/featureContainer.component.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ChangeDetectionStrategy, Component, ComponentFactoryResolver, ComponentRef, Input, OnChanges, Output, SimpleChanges, ViewContainerRef, EventEmitter } from "@angular/core"; -import { Subscription } from "rxjs"; -import { IFeature, RegionalFeaturesService } from "../regionalFeature.service"; -import { ISingleFeature } from "../singleFeatures/interfaces"; - -@Component({ - selector: 'feature-container', - template: '', - changeDetection: ChangeDetectionStrategy.OnPush, -}) - -export class FeatureContainer implements OnChanges{ - @Input() - feature: IFeature - - @Input() - region: any - - @Output() - viewChanged: EventEmitter<boolean> = new EventEmitter() - - private cr: ComponentRef<ISingleFeature> - - constructor( - private vCRef: ViewContainerRef, - private rService: RegionalFeaturesService, - private cfr: ComponentFactoryResolver, - ){ - } - - private viewChangedSub: Subscription - - ngOnChanges(simpleChanges: SimpleChanges){ - if (!simpleChanges.feature) return - const { currentValue, previousValue } = simpleChanges.feature - if (currentValue === previousValue) return - this.clear() - - /** - * catching instances where currentValue for feature is falsy - */ - if (!currentValue) return - - /** - * TODO catch if map is undefined - */ - const comp = this.rService.mapFeatToCmp.get(currentValue.type) - if (!comp) throw new Error(`mapFeatToCmp for ${currentValue.type} not defined`) - const cf = this.cfr.resolveComponentFactory<ISingleFeature>(comp) - this.cr = this.vCRef.createComponent(cf) - this.cr.instance.feature = this.feature - this.cr.instance.region = this.region - this.viewChangedSub = this.cr.instance.viewChanged.subscribe(() => this.viewChanged.emit(true)) - } - - clear(){ - if (this.viewChangedSub) this.viewChangedSub.unsubscribe() - this.vCRef.clear() - } -} diff --git a/src/atlasComponents/regionalFeatures/index.ts b/src/atlasComponents/regionalFeatures/index.ts deleted file mode 100644 index a71230d7107939f7da63d18d843339937ed89290..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { RegionalFeaturesModule } from './module' -export { IFeature } from './regionalFeature.service' \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/module.ts b/src/atlasComponents/regionalFeatures/module.ts deleted file mode 100644 index 573acb57dfd9b3f0f0e303a622be2926f8013f27..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/module.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { HttpClientModule } from "@angular/common/http"; -import { NgModule } from "@angular/core"; -import { UtilModule } from "src/util"; -import { AngularMaterialModule } from "src/sharedModules"; -import { FeatureContainer } from "./featureContainer/featureContainer.component"; -import { FilterRegionalFeaturesByTypePipe } from "./pipes/filterRegionalFeaturesByType.pipe"; -import { FilterRegionFeaturesById } from "./pipes/filterRegionFeaturesById.pipe"; -import { FindRegionFEatureById } from "./pipes/findRegionFeatureById.pipe"; -import { RegionalFeaturesService } from "./regionalFeature.service"; -import { RegionGetAllFeaturesDirective } from "./regionGetAllFeatures.directive"; -import { FeatureIEEGRecordings } from "./singleFeatures/iEEGRecordings/module"; -import { ReceptorDensityModule } from "./singleFeatures/receptorDensity/module"; - -@NgModule({ - imports: [ - CommonModule, - UtilModule, - AngularMaterialModule, - FeatureIEEGRecordings, - ReceptorDensityModule, - HttpClientModule, - ], - declarations: [ - /** - * components - */ - FeatureContainer, - - /** - * Directives - */ - RegionGetAllFeaturesDirective, - - /** - * pipes - */ - FilterRegionalFeaturesByTypePipe, - FindRegionFEatureById, - FilterRegionFeaturesById, - ], - exports: [ - RegionGetAllFeaturesDirective, - FilterRegionFeaturesById, - FeatureContainer, - ], - providers: [ - RegionalFeaturesService, - ] -}) - -export class RegionalFeaturesModule{} diff --git a/src/atlasComponents/regionalFeatures/pipes/filterRegionFeaturesById.pipe.ts b/src/atlasComponents/regionalFeatures/pipes/filterRegionFeaturesById.pipe.ts deleted file mode 100644 index ed20ef67cfc96dcf3f21b1789cc7cdf82ac2fa01..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/pipes/filterRegionFeaturesById.pipe.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { IFeature } from "../regionalFeature.service"; -import { getIdFromFullId } from 'common/util' -@Pipe({ - name: 'filterRegionFeaturesById', - pure: true -}) - -export class FilterRegionFeaturesById implements PipeTransform{ - public transform(features: IFeature[], id: string){ - const filterId = getIdFromFullId(id) - return features.filter(f => getIdFromFullId(f['@id']) === filterId) - } -} diff --git a/src/atlasComponents/regionalFeatures/pipes/filterRegionalFeaturesByType.pipe.ts b/src/atlasComponents/regionalFeatures/pipes/filterRegionalFeaturesByType.pipe.ts deleted file mode 100644 index f5bf231bddf4cbb5fa1ae97121c6315074b2305e..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/pipes/filterRegionalFeaturesByType.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { IFeature } from "../regionalFeature.service"; - -@Pipe({ - name: 'filterRegionalFeaturesBytype', - pure: true, -}) - -export class FilterRegionalFeaturesByTypePipe implements PipeTransform{ - public transform(array: IFeature[], featureType: string){ - return array.filter(f => featureType ? f.type === featureType : true ) - } -} diff --git a/src/atlasComponents/regionalFeatures/pipes/findRegionFeatureById.pipe.ts b/src/atlasComponents/regionalFeatures/pipes/findRegionFeatureById.pipe.ts deleted file mode 100644 index a083a28a5a843ef36c47c29183efd72ed7e28936..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/pipes/findRegionFeatureById.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { IFeature } from "../regionalFeature.service"; - -@Pipe({ - name: 'findRegionFeaturebyId', - pure: true -}) - -export class FindRegionFEatureById implements PipeTransform{ - public transform(features: IFeature[], id: string){ - return features.find(f => f['@id'] === id) - } -} diff --git a/src/atlasComponents/regionalFeatures/regionGetAllFeatures.directive.ts b/src/atlasComponents/regionalFeatures/regionGetAllFeatures.directive.ts deleted file mode 100644 index 920e1f0a98c7cd791514755d48351b63d4005684..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/regionGetAllFeatures.directive.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Directive, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core"; -import { Subscription } from "rxjs"; -import { RegionalFeaturesService } from "./regionalFeature.service"; -import { RegionFeatureBase } from "./singleFeatures/base/regionFeature.base"; - -@Directive({ - selector: '[region-get-all-features-directive]', - exportAs: 'rfGetAllFeatures' -}) - -export class RegionGetAllFeaturesDirective extends RegionFeatureBase implements OnDestroy, OnInit{ - @Output() - loadingStateChanged: EventEmitter<boolean> = new EventEmitter() - - private subscriptions: Subscription[] = [] - - /** - * since the base class has DI - * sub class needs to call super() with the correct DI - */ - constructor( - rfService: RegionalFeaturesService - ){ - super(rfService) - } - - ngOnInit(){ - this.subscriptions.push( - this.isLoading$.subscribe(val => { - this.loadingStateChanged.emit(val) - }) - ) - } - ngOnDestroy(){ - while (this.subscriptions.length > 0) this.subscriptions.pop().unsubscribe() - } -} diff --git a/src/atlasComponents/regionalFeatures/regionalFeature.service.ts b/src/atlasComponents/regionalFeatures/regionalFeature.service.ts deleted file mode 100644 index 0525ba80e033d9db9245d54f368b152b210fda11..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/regionalFeature.service.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { HttpClient } from "@angular/common/http"; -import { Inject, Injectable, OnDestroy, Optional } from "@angular/core"; -import { PureContantService } from "src/util"; -import { getIdFromFullId, getRegionHemisphere, getStringIdsFromRegion, flattenReducer } from 'common/util' -import { forkJoin, from, Observable, of, Subject, Subscription, throwError } from "rxjs"; -import { catchError, map, mapTo, shareReplay, switchMap } from "rxjs/operators"; -import { IHasId } from "src/util/interfaces"; -import { select, Store } from "@ngrx/store"; -import { viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors"; -import { viewerStateAddUserLandmarks, viewreStateRemoveUserLandmarks } from "src/services/state/viewerState/actions"; -import { uiStateMouseoverUserLandmark } from "src/services/state/uiState/selectors"; -import { APPEND_SCRIPT_TOKEN } from "src/util/constants"; - -const libraries = [ - 'https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js', - 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.1.2/es5/tex-svg.js' -] - -export interface IFeature extends IHasId{ - type: string - name: string - data?: IHasId[] -} - -@Injectable({ - providedIn: 'root' -}) - -export class RegionalFeaturesService implements OnDestroy{ - - public depScriptLoaded$: Observable<boolean> - - private subs: Subscription[] = [] - private templateSelected: any - constructor( - private http: HttpClient, - private pureConstantService: PureContantService, - private store$: Store<any>, - @Optional() @Inject(APPEND_SCRIPT_TOKEN) private appendScript: (src: string) => Promise<HTMLScriptElement> - ){ - this.subs.push( - this.store$.pipe( - select(viewerStateSelectedTemplateSelector) - ).subscribe(val => this.templateSelected = val) - ) - - this.depScriptLoaded$ = this.appendScript - ? from( - libraries.map(this.appendScript) - ).pipe( - mapTo(true), - catchError(() => of(false)), - shareReplay(1), - ) - : of(false) - } - - public mapFeatToCmp = new Map<string, any>() - - ngOnDestroy(){ - while (this.subs.length > 0) this.subs.pop().unsubscribe() - } - - public onHoverLandmarks$ = this.store$.pipe( - select(uiStateMouseoverUserLandmark) - ) - - public getAllFeaturesByRegion(_region: {['fullId']: string} | { id: { kg: {kgSchema: string, kgId: string} } }){ - - const region = { - ..._region, - } - if (!region['fullId']) { - const { kgSchema, kgId } = region['id']?.kg || {} - if (kgSchema && kgId) region['fullId'] = `${kgSchema}/${kgId}` - } - - if (!region['fullId']) throw new Error(`getAllFeaturesByRegion - region does not have fullId defined`) - const regionFullIds = getStringIdsFromRegion(region) - const hemisphereObj = (() => { - const hemisphere = getRegionHemisphere(region) - return hemisphere ? { hemisphere } : {} - })() - - const refSpaceObj = this.templateSelected && this.templateSelected.fullId - ? { referenceSpaceId: getIdFromFullId(this.templateSelected.fullId) } - : {} - - return forkJoin( - regionFullIds.map(regionFullId => this.http.get<{features: IHasId[]}>( - `${this.pureConstantService.backendUrl}regionalFeatures/byRegion/${encodeURIComponent( regionFullId )}`, - { - params: { - ...hemisphereObj, - ...refSpaceObj, - }, - responseType: 'json' - } - ).pipe( - switchMap(({ features }) => forkJoin( - features.map(({ ['@id']: featureId }) => - this.http.get<IFeature>( - `${this.pureConstantService.backendUrl}regionalFeatures/byRegion/${encodeURIComponent( regionFullId )}/${encodeURIComponent( featureId )}`, - { - params: { - ...hemisphereObj, - ...refSpaceObj, - }, - responseType: 'json' - } - ) - ) - )), - )) - ).pipe( - map((arr: IFeature[][]) => arr.reduce(flattenReducer, [])) - ) - } - - public getFeatureData(region: any,feature: IFeature, data: IHasId){ - if (!feature['@id']) throw new Error(`@id attribute for feature is required`) - if (!data['@id']) throw new Error(`@id attribute for data is required`) - const refSpaceObj = this.templateSelected && this.templateSelected.fullId - ? { referenceSpaceId: getIdFromFullId(this.templateSelected.fullId) } - : {} - const hemisphereObj = (() => { - const hemisphere = getRegionHemisphere(region) - return hemisphere ? { hemisphere } : {} - })() - - const regionId = getIdFromFullId(region && region.fullId) - const url = regionId - ? `${this.pureConstantService.backendUrl}regionalFeatures/byRegion/${encodeURIComponent(regionId)}/${encodeURIComponent(feature['@id'])}/${encodeURIComponent(data['@id'])}` - : `${this.pureConstantService.backendUrl}regionalFeatures/byFeature/${encodeURIComponent(feature['@id'])}/${encodeURIComponent(data['@id'])}` - return this.http.get<IHasId>( - url, - { - params: { - ...hemisphereObj, - ...refSpaceObj, - }, - responseType: 'json' - } - ) - } - - public addLandmarks(lms: IHasId[]) { - this.store$.dispatch( - viewerStateAddUserLandmarks({ - landmarks: lms.map(lm => ({ - ...lm, - id: lm['@id'], - name: `region feature: ${lm['@id']}` - })) - }) - ) - } - - public removeLandmarks(lms: IHasId[]) { - this.store$.dispatch( - viewreStateRemoveUserLandmarks({ - payload: { - landmarkIds: lms.map(l => l['@id']) - } - }) - ) - } - - showDatafeatureInfo$ = new Subject<{ fullId: string } | { name: string, description: string }>() -} diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/base/regionFeature.base.ts b/src/atlasComponents/regionalFeatures/singleFeatures/base/regionFeature.base.ts deleted file mode 100644 index 21dde06920b0ddf29f384683c5851de9a171445f..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/base/regionFeature.base.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Directive, EventEmitter, Input, Output, SimpleChanges } from "@angular/core" -import { BehaviorSubject, forkJoin, Observable, of } from "rxjs" -import { catchError, shareReplay, switchMap, tap } from "rxjs/operators" -import { IHasId } from "src/util/interfaces" -import { IFeature, RegionalFeaturesService } from "../../regionalFeature.service" - -@Directive() -export class RegionFeatureBase{ - - private _feature: IFeature - - private feature$ = new BehaviorSubject(null) - @Input() - set feature(val) { - this._feature = val - this.feature$.next(val) - } - get feature(){ - return this._feature - } - - @Input() - public region: any - - @Output('feature-explorer-is-loading') - public dataIsLoadingEventEmitter: EventEmitter<boolean> = new EventEmitter() - - public features: IFeature[] = [] - public data$: Observable<IHasId[]> - - /** - * using isLoading flag for conditional rendering of root element (or display loading spinner) - * this is necessary, or the transcluded tab will always be the active tab, - * as this.features as populated via async - */ - public isLoading$ = new BehaviorSubject(false) - private _isLoading: boolean = false - get isLoading(){ - return this._isLoading - } - set isLoading(val){ - if (val !== this._isLoading) - this._isLoading = val - this.isLoading$.next(val) - } - - public dataIsLoading$ = new BehaviorSubject(false) - private _dataIsLoading = false - set dataIsLoading(val) { - if (val === this._dataIsLoading) return - this._dataIsLoading = val - this.dataIsLoading$.next(val) - this.dataIsLoadingEventEmitter.emit(val) - } - get dataIsLoading(){ - return this._dataIsLoading - } - - ngOnChanges(changes: SimpleChanges){ - if (changes.region && changes.region.previousValue !== changes.region.currentValue) { - this.isLoading = true - this.features = [] - - const _ = (changes.region.currentValue - ? this._regionalFeatureService.getAllFeaturesByRegion(changes.region.currentValue) - : of([]) - ).pipe( - /** - * region may have no fullId defined - * in this case, just emit [] - */ - catchError(() => of([])) - ).subscribe({ - next: features => this.features = features, - complete: () => this.isLoading = false - }) - } - } - - constructor( - private _regionalFeatureService: RegionalFeaturesService - ){ - - /** - * once feature stops loading, watch for input feature - */ - this.data$ = this.feature$.pipe( - tap(() => this.dataIsLoading = true), - switchMap((feature: IFeature) => forkJoin( - feature.data.map(datum => this._regionalFeatureService.getFeatureData(this.region, feature, datum))) - ), - tap(() => this.dataIsLoading = false), - shareReplay(1), - ) - } -} diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts b/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts deleted file mode 100644 index 11fd197fbd45b569e35f15f5a9ae54ab13f07777..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.component.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { Component, Inject, Optional, EventEmitter } from "@angular/core"; -import { Store } from "@ngrx/store"; -import { merge, Subject, Subscription } from "rxjs"; -import { debounceTime, map, scan, take } from "rxjs/operators"; -import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions"; -import { RegionalFeaturesService } from "src/atlasComponents/regionalFeatures/regionalFeature.service"; -import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; -import { IHasId } from "src/util/interfaces"; -import { RegionFeatureBase } from "../../base/regionFeature.base"; -import { ISingleFeature } from '../../interfaces' - -const selectedColor = [ 255, 0, 0 ] - -@Component({ - templateUrl: './iEEGRecordings.template.html', - styleUrls: [ - './iEEGRecordings.style.css' - ] -}) - -export class IEEGRecordingsCmp extends RegionFeatureBase implements ISingleFeature{ - private landmarksLoaded: IHasId[] = [] - private onDestroyCb: (() => void)[] = [] - private sub: Subscription[] = [] - - constructor( - private regionFeatureService: RegionalFeaturesService, - private store: Store<any>, - @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) private regClickIntp: ClickInterceptor, - ){ - super(regionFeatureService) - } - - public viewChanged = new EventEmitter<boolean>() - - ngOnInit(){ - if (this.regClickIntp) { - const { deregister, register } = this.regClickIntp - const clickIntp = this.clickIntp.bind(this) - register(clickIntp) - this.onDestroyCb.push(() => { - deregister(clickIntp) - }) - } - this.sub.push( - this.data$.subscribe(data => { - const landmarksTobeLoaded: IHasId[] = [] - - for (const datum of data) { - const electrodeId = datum['@id'] - landmarksTobeLoaded.push( - ...datum['contactPoints'].map(({ ['@id']: contactPtId, position }) => { - return { - _: { - electrodeId, - contactPtId - }, - ['@id']: `${electrodeId}#${contactPtId}`, - position - } - }) - ) - } - /** - * remove first, then add - */ - if (this.landmarksLoaded.length > 0) this.regionFeatureService.removeLandmarks(this.landmarksLoaded) - if (landmarksTobeLoaded.length > 0) this.regionFeatureService.addLandmarks(landmarksTobeLoaded) - this.landmarksLoaded = landmarksTobeLoaded - }) - ) - - this.sub.push( - this.dataIsLoading$.subscribe(() => this.viewChanged.emit(true)) - ) - - this.onDestroyCb.push(() => { - if (this.landmarksLoaded.length > 0) this.regionFeatureService.removeLandmarks(this.landmarksLoaded) - }) - - this.sub.push( - this.openElectrodeId$.pipe( - debounceTime(200) - ).subscribe(arr => { - - if (this.landmarksLoaded.length > 0) { - this.regionFeatureService.removeLandmarks(this.landmarksLoaded) - this.regionFeatureService.addLandmarks(this.landmarksLoaded.map(lm => { - const selected = arr.some(id => id === lm['_']['electrodeId']) - return { - ...lm, - color: selected ? selectedColor : null, - showInSliceView: selected - } - })) - } - }) - ) - } - - - ngOnDestroy(){ - while(this.onDestroyCb.length > 0) this.onDestroyCb.pop()() - while(this.sub.length > 0) this.sub.pop().unsubscribe() - } - - handleContactPtClk(contactPt: IHasId & { position: number[] }){ - const { position } = contactPt - this.store.dispatch( - viewerStateChangeNavigation({ - navigation: { - position: position.map(v => v * 1e6), - positionReal: true, - animation: {} - }, - }) - ) - } - - handleDatumExpansion(electrodeId: string, open: boolean){ - /** - * TODO either debounce call here, or later down stream - */ - if (open) this.exploreElectrode$.next(electrodeId) - else this.unExploreElectrode$.next(electrodeId) - } - - private unExploreElectrode$ = new Subject<string>() - private exploreElectrode$ = new Subject<string>() - public openElectrodeId$ = merge( - this.unExploreElectrode$.pipe( - map(id => ({ - add: null, - remove: id - })) - ), - this.exploreElectrode$.pipe( - map(id => ({ - add: id, - remove: null - })) - ) - ).pipe( - scan((acc, curr) => { - const { add, remove } = curr - const set = new Set(acc) - if (add) set.add(add) - if (remove) set.delete(remove) - return Array.from(set) - }, []) - ) - - private clickIntp(ev: any): boolean { - let hoveredLandmark = null - this.regionFeatureService.onHoverLandmarks$.pipe( - take(1) - ).subscribe(val => { - hoveredLandmark = val - }) - if (!hoveredLandmark) return true - const isOne = this.landmarksLoaded.some(lm => { - return lm['_']['electrodeId'] === hoveredLandmark['_']['electrodeId'] - }) - if (!isOne) return true - this.exploreElectrode$.next(hoveredLandmark['_']['electrodeId']) - } -} diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.template.html b/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.template.html deleted file mode 100644 index 7a4b70d88a55e2e8325446c946c2035ed29d59bf..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.template.html +++ /dev/null @@ -1,56 +0,0 @@ -<ng-container *ngIf="!dataIsLoading; else loadingTmpl"> - - <mat-accordion - class="ml-24px-n mr-24px-n d-block"> - <mat-expansion-panel *ngFor="let datum of (data$ | async)" - [expanded]="openElectrodeId$ | async | includes : datum['@id']" - (opened)="handleDatumExpansion(datum['@id'], true)" - (closed)="handleDatumExpansion(datum['@id'], false)" - togglePosition="before"> - <mat-expansion-panel-header> - <mat-panel-title> - Electrode - </mat-panel-title> - <mat-panel-description class="text-nowrap"> - {{ datum['@id'] }} - </mat-panel-description> - </mat-expansion-panel-header> - - <label for="task-list" class="d-block mat-h4 mt-4 text-muted"> - Tasks - </label> - <section class="d-flex align-items-center mt-1"> - <section id="task-list" class="flex-grow-1 flex-shrink-1 overflow-x-auto"> - <div role="list"> - <mat-chip *ngFor="let task of datum['tasks']" class="ml-1"> - {{ task }} - </mat-chip> - </div> - </section> - </section> - - <label for="contact-points-list" class="d-block mat-h4 mt-4 text-muted"> - Contact Points - </label> - <section class="d-flex align-items-center mt-1"> - <section id="contact-points-list" class="flex-grow-1 flex-shrink-1 overflow-x-auto"> - <div role="list"> - <mat-chip *ngFor="let contactPt of datum['contactPoints']" - [matTooltip]="contactPt['position']" - (click)="handleContactPtClk(contactPt)" - class="ml-1"> - {{ contactPt['@id'] }} - </mat-chip> - </div> - </section> - </section> - - </mat-expansion-panel> - </mat-accordion> - -</ng-container> - -<!-- loading template --> -<ng-template #loadingTmpl> - <spinner-cmp></spinner-cmp> -</ng-template> diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/module.ts b/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/module.ts deleted file mode 100644 index 76d6cd9f67dba1c93bc6e51ee9c845bb035d4766..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { ComponentsModule } from "src/components"; -import { AngularMaterialModule } from "src/sharedModules"; -import { UtilModule } from "src/util"; -import { RegionalFeaturesService } from "../../regionalFeature.service"; -import { IEEGRecordingsCmp } from "./iEEGRecordings/iEEGRecordings.component"; - -@NgModule({ - imports: [ - CommonModule, - UtilModule, - AngularMaterialModule, - ComponentsModule, - ], - declarations: [ - IEEGRecordingsCmp - ], - exports: [ - IEEGRecordingsCmp - ] -}) - -export class FeatureIEEGRecordings{ - constructor( - rService: RegionalFeaturesService - ){ - rService.mapFeatToCmp.set('iEEG recording', IEEGRecordingsCmp) - } -} diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/interfaces.ts b/src/atlasComponents/regionalFeatures/singleFeatures/interfaces.ts deleted file mode 100644 index bd3861d391b4bcc90d262ee16e124b51e4affee2..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/interfaces.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { EventEmitter } from "@angular/core"; -import { IFeature } from "../regionalFeature.service"; - -export interface ISingleFeature{ - feature: IFeature - region: any - viewChanged: EventEmitter<boolean> -} diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/filterReceptorBytype.pipe.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/filterReceptorBytype.pipe.ts deleted file mode 100644 index 1b0a91dd80079acf6e3cc6709c4ac3cc74ec5f97..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/filterReceptorBytype.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { IHasId } from "src/util/interfaces"; - -@Pipe({ - name: 'filterReceptorByType', - pure: true -}) - -export class FilterReceptorByType implements PipeTransform{ - public transform(arr: IHasId[], qualifer: string): IHasId[]{ - return (arr || []).filter(({ ['@id']: dId }) => dId.indexOf(qualifer) >= 0) - } -} diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getAllReceptors.pipe.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getAllReceptors.pipe.ts deleted file mode 100644 index ba3016186957b4851e469792cc834110152ddb1b..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getAllReceptors.pipe.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { IHasId } from "src/util/interfaces"; - -@Pipe({ - name: 'getAllReceptors', - pure: true -}) - -export class GetAllReceptorsPipe implements PipeTransform{ - public transform(arr: IHasId[]): string[]{ - return (arr || []).reduce((acc, curr) => { - const thisType = /_(pr|ar)_([a-zA-Z0-9_]+)\./.exec(curr['@id']) - if (!thisType) return acc - return new Set(acc).has(thisType) ? acc : acc.concat(thisType[2]) - }, []) - } -} \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getId.pipe.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getId.pipe.ts deleted file mode 100644 index 3af1a02bd4a98455bc9b0166ff4adfc5afa53646..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getId.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name: 'getId', - pure: true -}) - -export class GetIdPipe implements PipeTransform{ - public transform(fullId: string): string{ - const re = /\/([a-f0-9-]+)$/.exec(fullId) - return (re && re[1]) || null - } -} diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getUrl.pipe.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getUrl.pipe.ts deleted file mode 100644 index f4fdfab5613c225aa293a05b45c9d0ab8be666ab..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/getUrl.pipe.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { IHasId } from "src/util/interfaces"; - -interface IReceptorDatum extends IHasId{ - ['@context']: { - [key: string]: string - } - filename: string - mimetype: string - url: string | { - url: string - ['receptors.tsv']: string - } -} - -interface IHRef{ - url: string - filename: string -} - -@Pipe({ - name: 'getUrls', - pure: true -}) - -export class GetUrlsPipe implements PipeTransform{ - public transform(input: IReceptorDatum): IHRef[]{ - const output: IHRef[] = [] - let _url = typeof input.url === 'string' - ? input.url - : input.url.url - - for (const key in (input['@context'] || {})) { - _url = _url.replace(`${key}:`, input['@context'][key]) - } - - const match = /\/([\w-.]+)$/.exec(_url) - output.push({ - url: _url, - filename: match ? match[1] : 'download' - }) - return output - } -} - diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/module.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/module.ts deleted file mode 100644 index e8ab67e4273b33310171ba1b36f7199bf5286008..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; -import { AngularMaterialModule } from "src/sharedModules"; -import { RegionalFeaturesService } from "../../regionalFeature.service"; -import { FilterReceptorByType } from "./filterReceptorBytype.pipe"; -import { GetAllReceptorsPipe } from "./getAllReceptors.pipe"; -import { GetIdPipe } from "./getId.pipe"; -import { GetUrlsPipe } from "./getUrl.pipe"; -import { ReceptorDensityFeatureCmp } from "./receptorDensity/receptorDensity.component"; - -@NgModule({ - imports: [ - CommonModule, - AngularMaterialModule, - ], - declarations: [ - ReceptorDensityFeatureCmp, - FilterReceptorByType, - GetIdPipe, - GetAllReceptorsPipe, - GetUrlsPipe, - ], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA - ] -}) - -export class ReceptorDensityModule{ - constructor( - rService: RegionalFeaturesService - ){ - rService.mapFeatToCmp.set(`Receptor density measurement`, ReceptorDensityFeatureCmp) - } -} diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.component.ts b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.component.ts deleted file mode 100644 index 52cda125d7aa02bfaa062a121b62b94d365fa612..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.component.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Component, ElementRef, EventEmitter, HostListener, OnDestroy, Optional } from "@angular/core"; -import { fromEvent, Observable, of, Subscription } from "rxjs"; -import { RegionalFeaturesService } from "src/atlasComponents/regionalFeatures/regionalFeature.service"; -import { PureContantService } from "src/util"; -import { RegionFeatureBase } from "../../base/regionFeature.base"; -import { ISingleFeature } from "../../interfaces"; -import { CONST } from 'common/constants' -import { environment } from 'src/environments/environment' - -const { - RECEPTOR_FP_CAPTION, - RECEPTOR_PR_CAPTION, - RECEPTOR_AR_CAPTION, -} = CONST -@Component({ - templateUrl: './receptorDensity.template.html', - styleUrls: [ - './receptorDensity.style.css' - ] -}) - -export class ReceptorDensityFeatureCmp extends RegionFeatureBase implements ISingleFeature, OnDestroy{ - - public RECEPTOR_FP_CAPTION = RECEPTOR_FP_CAPTION - public RECEPTOR_PR_CAPTION = RECEPTOR_PR_CAPTION - public RECEPTOR_AR_CAPTION = RECEPTOR_AR_CAPTION - - public DS_PREVIEW_URL = environment.DATASET_PREVIEW_URL - viewChanged: EventEmitter<null> = new EventEmitter() - - private WEB_COMPONENT_MOUSEOVER_EVENT_NAME = 'kg-ds-prv-regional-feature-mouseover' - private webComponentOnHover: string = null - - public selectedReceptor: string - - public darktheme$: Observable<boolean> - - private subs: Subscription[] = [] - public depScriptLoaded$: Observable<boolean> - constructor( - regService: RegionalFeaturesService, - el: ElementRef, - @Optional() pureConstantService: PureContantService - ){ - super(regService) - this.depScriptLoaded$ = regService.depScriptLoaded$ - if (pureConstantService) { - this.darktheme$ = pureConstantService.darktheme$ - } else { - this.darktheme$ = of(false) - } - - this.subs.push( - fromEvent(el.nativeElement, this.WEB_COMPONENT_MOUSEOVER_EVENT_NAME).subscribe((ev: CustomEvent) => { - this.webComponentOnHover = ev.detail?.data?.receptor?.label - }) - ) - } - - @HostListener('click') - onClick(){ - if (this.webComponentOnHover) { - this.selectedReceptor = this.webComponentOnHover - } - } - - ngOnDestroy(){ - while(this.subs.length > 0) this.subs.pop().unsubscribe() - } -} diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.style.css b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.style.css deleted file mode 100644 index 0437be86256eb2a7039e72e33e80de0a92ea62ef..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.style.css +++ /dev/null @@ -1,11 +0,0 @@ -kg-dataset-previewer -{ - display: block; - height: 20em; -} - -kg-ds-prv-regional-feature-view -{ - display: block; - min-height: 20em; -} diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.template.html b/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.template.html deleted file mode 100644 index f1454e06ca2bef47d6629a80a3907497b650ed76..0000000000000000000000000000000000000000 --- a/src/atlasComponents/regionalFeatures/singleFeatures/receptorDensity/receptorDensity/receptorDensity.template.html +++ /dev/null @@ -1,143 +0,0 @@ -<label for="fingerprint-cmp" class="d-inline mat-h4 mt-4 text-muted"> - Fingerprint : -</label> - -<ng-container *ngFor="let datum of (data$ | async | filterReceptorByType : '_fp_')"> - - <ng-container *ngTemplateOutlet="labelTmpl; context: { - for: 'fingerprint-cmp', - label: RECEPTOR_FP_CAPTION - }"> - </ng-container> - - <div class="d-block"> - <ng-container *ngTemplateOutlet="datasetPreviewTmpl; context: { - id: 'fingerprint-cmp', - kgId: (feature['@id'] | getId), - filename: datum['@id'], - datum: datum - }"> - </ng-container> - </div> - -</ng-container> - -<mat-divider></mat-divider> - -<ng-container *ngIf="data$ | async | getAllReceptors as allReceptors; else selectPlaceHolderTmpl"> - - <mat-form-field class="mt-2 w-100" *ngIf="allReceptors.length > 0; else selectPlaceHolderTmpl"> - <mat-label> - Select a receptor - </mat-label> - <mat-select [(value)]="selectedReceptor"> - <mat-option - *ngFor="let receptor of allReceptors" - [value]="receptor"> - {{ receptor }} - </mat-option> - </mat-select> - </mat-form-field> - -</ng-container> - -<ng-template #selectPlaceHolderTmpl> - <span class="text-muted">No profile or autoradiographs available.</span> -</ng-template> - -<ng-template [ngIf]="selectedReceptor"> - <ng-container *ngTemplateOutlet="prArTmpl; context: { filter: '_pr_', label: 'Profile' }"> - </ng-container> - <ng-container *ngTemplateOutlet="prArTmpl; context: { filter: '_ar_', label: 'Autoradiograph' }"> - </ng-container> -</ng-template> - -<!-- ar/pr template --> -<ng-template #prArTmpl let-label="label" let-filter="filter"> - <ng-container *ngFor="let datum of (data$ | async | filterReceptorByType : selectedReceptor | filterReceptorByType : filter); let first = first"> - <ng-template [ngIf]="first"> - <label [attr.for]="label + '-cmp'" class="d-inline mat-h4 mt-4 text-muted"> - {{ label }} : - </label> - </ng-template> - - <ng-container *ngTemplateOutlet="labelTmpl; context: { - for: label + '-cmp', - label: 'Autoradiograph' ? RECEPTOR_AR_CAPTION : RECEPTOR_PR_CAPTION - }"> - </ng-container> - - <div class="d-block"> - <ng-container *ngTemplateOutlet="datasetPreviewTmpl; context: { - id: label + '-cmp', - kgId: (feature['@id'] | getId), - filename: datum['@id'], - datum: datum - }"> - </ng-container> - </div> - - </ng-container> -</ng-template> - - -<!-- display preview tmpl --> -<ng-template #datasetPreviewTmpl - let-id="id" - let-kgId="kgId" - let-filename="filename" - let-datum="datum"> - - <!-- download btns --> - <ng-container *ngFor="let urlObj of datum | getUrls"> - <ng-container *ngTemplateOutlet="downloadBtnTmpl; context: { - url: urlObj.url, - filename: urlObj.filename, - tooltip: 'download ' + urlObj.filename - }"> - </ng-container> - </ng-container> - - <!-- render preview --> - <kg-ds-prv-regional-feature-view - *ngIf="depScriptLoaded$ | async; else fallbackTmpl" - [attr.id]="id" - [darkmode]="darktheme$ | async" - (renderEvent)="viewChanged.emit()" - [backendUrl]="DS_PREVIEW_URL" - [kgId]="kgId" - [filename]="filename"> - </kg-ds-prv-regional-feature-view> - - <ng-template #fallbackTmpl> - <kg-dataset-previewer - [attr.id]="id" - (renderEvent)="viewChanged.emit()" - [backendUrl]="DS_PREVIEW_URL" - [kgId]="kgId" - [filename]="filename"> - </kg-dataset-previewer> - </ng-template> -</ng-template> - -<ng-template #downloadBtnTmpl - let-url="url" - let-filename="filename" - let-tooltip="tooltip"> - <a [href]="url" - class="d-inline-block" - [download]="filename" - [matTooltip]="tooltip"> - <button mat-icon-button> - <i class="fas fa-download"></i> - </button> - </a> -</ng-template> - -<ng-template #labelTmpl let-label="label" let-for="for"> - <label [attr.for]="for" class="d-inline text-muted"> - <span> - {{ label }} - </span> - </label> -</ng-template> \ No newline at end of file diff --git a/src/atlasComponents/sapi/constants.ts b/src/atlasComponents/sapi/constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..b31cfc1b14703de11f2c5e2efaee1cacb369347b --- /dev/null +++ b/src/atlasComponents/sapi/constants.ts @@ -0,0 +1,16 @@ +export const IDS = { + ATLAES: { + HUMAN: "juelich/iav/atlas/v1.0.0/1", + RAT: "minds/core/parcellationatlas/v1.0.0/522b368e-49a3-49fa-88d3-0870a307974a", + }, + TEMPLATES: { + BIG_BRAIN: "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588", + MNI152: "minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2", + COLIN27: "minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992", + WAXHOLM: "minds/core/referencespace/v1.0.0/d5717c4a-0fa1-46e6-918c-b8003069ade8", + }, + PARCELLATION: { + JBA29: "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290", + WAXHOLMV4: "minds/core/parcellationatlas/v1.0.0/ebb923ba-b4d5-4b82-8088-fa9215c2e1fe-v4", + } +} diff --git a/src/atlasComponents/sapi/core/index.ts b/src/atlasComponents/sapi/core/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..a7fc4cbb696db25e9fa2159b5862e23c0bdb260e --- /dev/null +++ b/src/atlasComponents/sapi/core/index.ts @@ -0,0 +1,4 @@ +export { SAPIAtlas } from "./sapiAtlas" +export { SAPISpace } from "./sapiSpace" +export { SAPIParcellation } from "./sapiParcellation" +export { SAPIRegion } from "./sapiRegion" diff --git a/src/atlasComponents/sapi/core/sapiAtlas.ts b/src/atlasComponents/sapi/core/sapiAtlas.ts new file mode 100644 index 0000000000000000000000000000000000000000..788a803bd32eb8cf8b5dcc98545284abbf4fe056 --- /dev/null +++ b/src/atlasComponents/sapi/core/sapiAtlas.ts @@ -0,0 +1,5 @@ +import { SAPI } from "../sapi.service"; + +export class SAPIAtlas{ + constructor(private sapi: SAPI, public id: string){} +} diff --git a/src/atlasComponents/sapi/core/sapiParcellation.ts b/src/atlasComponents/sapi/core/sapiParcellation.ts new file mode 100644 index 0000000000000000000000000000000000000000..4767cc2897ab0ed6c6567f3ee727ad2f73fece04 --- /dev/null +++ b/src/atlasComponents/sapi/core/sapiParcellation.ts @@ -0,0 +1,62 @@ +import { Observable } from "rxjs" +import { SapiVolumeModel } from ".." +import { SAPI } from "../sapi.service" +import {SapiParcellationFeatureModel, SapiParcellationModel, SapiQueryPriorityArg, SapiRegionModel} from "../type" + +type PaginationQuery = { + size: number + page: number +} + +type ParcellationPaginationQuery = { + type?: string + size?: number + page: number +} + +export class SAPIParcellation{ + constructor(private sapi: SAPI, public atlasId: string, public id: string){ + + } + + getDetail(queryParam?: SapiQueryPriorityArg): Observable<SapiParcellationModel>{ + return this.sapi.httpGet<SapiParcellationModel>( + `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}`, + null, + queryParam + ) + } + + getRegions(spaceId: string, queryParam?: SapiQueryPriorityArg): Observable<SapiRegionModel[]> { + return this.sapi.httpGet<SapiRegionModel[]>( + `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/regions`, + { + space_id: spaceId + }, + queryParam + ) + } + getVolumes(): Observable<SapiVolumeModel[]>{ + return this.sapi.httpGet<SapiVolumeModel[]>( + `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/volumes` + ) + } + + getFeatures(parcPagination?: ParcellationPaginationQuery, queryParam?: SapiQueryPriorityArg): Observable<SapiParcellationFeatureModel[]> { + return this.sapi.httpGet<SapiParcellationFeatureModel[]>( + `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features`, + { + type: parcPagination?.type, + size: parcPagination?.size?.toString() || '5', + page: parcPagination?.page.toString() || '0', + }, + queryParam + ) + } + + getFeatureInstance(instanceId: string): Observable<SapiParcellationFeatureModel> { + return this.sapi.http.get<SapiParcellationFeatureModel>( + `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.id)}/features/${encodeURIComponent(instanceId)}`, + ) + } +} diff --git a/src/atlasComponents/sapi/core/sapiRegion.ts b/src/atlasComponents/sapi/core/sapiRegion.ts new file mode 100644 index 0000000000000000000000000000000000000000..8248992c16ef86a1bbd6e28c395b8dd08db9d66f --- /dev/null +++ b/src/atlasComponents/sapi/core/sapiRegion.ts @@ -0,0 +1,105 @@ +import { SAPI } from ".."; +import { SapiRegionalFeatureModel, SapiRegionMapInfoModel, SapiRegionModel, cleanIeegSessionDatasets, SapiIeegSessionModel, CleanedIeegDataset, SapiVolumeModel, PaginatedResponse } from "../type"; +import { strToRgb, hexToRgb } from 'common/util' +import { merge, Observable, of } from "rxjs"; +import { catchError, map, scan } from "rxjs/operators"; + +export class SAPIRegion{ + + static GetDisplayColor(region: SapiRegionModel): [number, number, number]{ + if (!region) { + throw new Error(`region must be provided!`) + } + if (region.hasAnnotation?.displayColor) { + return hexToRgb(region.hasAnnotation.displayColor) + } + return strToRgb(JSON.stringify(region)) + } + + private prefix: string + + constructor( + private sapi: SAPI, + public atlasId: string, + public parcId: string, + public id: string, + ){ + this.prefix = `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/parcellations/${encodeURIComponent(this.parcId)}/regions/${encodeURIComponent(this.id)}` + } + + getFeatures(spaceId: string): Observable<(SapiRegionalFeatureModel | CleanedIeegDataset)[]> { + return merge( + this.sapi.httpGet<SapiRegionalFeatureModel[]>( + `${this.prefix}/features`, + { + space_id: spaceId + } + ).pipe( + catchError((err, obs) => { + return of([]) + }) + ), + spaceId + ? this.sapi.getSpace(this.atlasId, spaceId).getFeatures({ parcellationId: this.parcId, region: this.id }).pipe( + catchError((err, obs) => { + return of([]) + }), + map(feats => { + const ieegSessions: SapiIeegSessionModel[] = feats.filter(feat => feat["@type"] === "siibra/features/ieegSession") + return cleanIeegSessionDatasets(ieegSessions) + }), + ) + : of([] as CleanedIeegDataset[]) + ).pipe( + scan((acc, curr) => [...acc, ...curr], []) + ) + } + + getFeatureInstance(instanceId: string, spaceId: string = null): Observable<SapiRegionalFeatureModel> { + return this.sapi.httpGet<SapiRegionalFeatureModel>( + `${this.prefix}/features/${encodeURIComponent(instanceId)}`, + { + space_id: spaceId + } + ) + } + + getMapInfo(spaceId: string): Observable<SapiRegionMapInfoModel> { + return this.sapi.http.get<SapiRegionMapInfoModel>( + `${this.prefix}/regional_map/info`, + { + params: { + space_id: spaceId + } + } + ) + } + + getMapUrl(spaceId: string): string { + return `${this.prefix}/regional_map/map?space_id=${encodeURI(spaceId)}` + } + + getVolumes(): Observable<PaginatedResponse<SapiVolumeModel>>{ + const url = `${this.prefix}/volumes` + return this.sapi.httpGet<PaginatedResponse<SapiVolumeModel>>( + url + ) + } + + getVolumeInstance(volumeId: string): Observable<SapiVolumeModel> { + const url = `${this.prefix}/volumes/${encodeURIComponent(volumeId)}` + return this.sapi.httpGet<SapiVolumeModel>( + url + ) + } + + getDetail(spaceId: string): Observable<SapiRegionModel> { + const url = `${this.prefix}/${encodeURIComponent(this.id)}` + return this.sapi.httpGet<SapiRegionModel>( + url, + { + space_id: spaceId + } + ) + } +} diff --git a/src/atlasComponents/sapi/core/sapiSpace.ts b/src/atlasComponents/sapi/core/sapiSpace.ts new file mode 100644 index 0000000000000000000000000000000000000000..3effd6f16345aa82eebd3cf94b9f83118fb9546e --- /dev/null +++ b/src/atlasComponents/sapi/core/sapiSpace.ts @@ -0,0 +1,70 @@ +import { Observable } from "rxjs" +import { SAPI } from '../sapi.service' +import { camelToSnake } from 'common/util' +import {SapiQueryPriorityArg, SapiSpaceModel, SapiSpatialFeatureModel, SapiVolumeModel} from "../type" + +type FeatureResponse = { + features: { + [key: string]: string + } +} + +type RegionalSpatialFeatureOpts = { + parcellationId: string + region: string +} + +type BBoxSpatialFEatureOpts = { + bbox: string +} + +type SpatialFeatureOpts = RegionalSpatialFeatureOpts | BBoxSpatialFEatureOpts + +export class SAPISpace{ + + constructor(private sapi: SAPI, public atlasId: string, public id: string){} + + getModalities(param?: SapiQueryPriorityArg): Observable<FeatureResponse> { + return this.sapi.httpGet<FeatureResponse>( + `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/features`, + null, + param + ) + } + + getFeatures(opts: SpatialFeatureOpts): Observable<SapiSpatialFeatureModel[]> { + const query: Record<string, string> = {} + for (const [key, value] of Object.entries(opts)) { + query[camelToSnake(key)] = value + } + return this.sapi.httpGet<SapiSpatialFeatureModel[]>( + `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/features`, + query + ) + } + + getFeatureInstance(instanceId: string, opts: SpatialFeatureOpts): Observable<SapiSpatialFeatureModel> { + const query: Record<string, string> = {} + for (const [key, value] of Object.entries(opts)) { + query[camelToSnake(key)] = value + } + return this.sapi.httpGet<SapiSpatialFeatureModel>( + `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/features/${encodeURIComponent(instanceId)}`, + query + ) + } + + getDetail(param?: SapiQueryPriorityArg): Observable<SapiSpaceModel>{ + return this.sapi.httpGet<SapiSpaceModel>( + `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}`, + null, + param + ) + } + + getVolumes(): Observable<SapiVolumeModel[]>{ + return this.sapi.httpGet<SapiVolumeModel[]>( + `${this.sapi.bsEndpoint}/atlases/${encodeURIComponent(this.atlasId)}/spaces/${encodeURIComponent(this.id)}/volumes`, + ) + } +} diff --git a/src/atlasComponents/sapi/core/space/interSpaceCoordXform.service.spec.ts b/src/atlasComponents/sapi/core/space/interSpaceCoordXform.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..30542d6fc0441e31a41fc7ac5b372208ede998d1 --- /dev/null +++ b/src/atlasComponents/sapi/core/space/interSpaceCoordXform.service.spec.ts @@ -0,0 +1,114 @@ +import { InterSpaceCoordXformSvc, VALID_TEMPLATE_SPACE_NAMES } from './interSpaceCoordXform.service' +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing' +import { TestBed, fakeAsync, tick } from '@angular/core/testing' + +describe('InterSpaceCoordXformSvc.service.spec.ts', () => { + describe('InterSpaceCoordXformSvc', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule + ], + providers: [ + InterSpaceCoordXformSvc + ] + }) + }) + + afterEach(() => { + const ctrl = TestBed.inject(HttpTestingController) + ctrl.verify() + }) + + describe('#transform', () => { + it('should instantiate service properly', () => { + const service = TestBed.inject(InterSpaceCoordXformSvc) + expect(service).toBeTruthy() + expect(service.transform).toBeTruthy() + }) + + it('should transform argument properly', () => { + const service = TestBed.inject(InterSpaceCoordXformSvc) + const httpTestingController = TestBed.inject(HttpTestingController) + + // subscriptions are necessary for http fetch to occur + service.transform( + VALID_TEMPLATE_SPACE_NAMES.MNI152, + VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, + [1,2,3] + ).subscribe((_ev) => { + + }) + const req = httpTestingController.expectOne(service['url']) + expect(req.request.method).toEqual('POST') + expect( + JSON.parse(req.request.body) + ).toEqual({ + 'source_space': VALID_TEMPLATE_SPACE_NAMES.MNI152, + 'target_space': VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, + 'source_points': [ + [1e-6, 2e-6, 3e-6] + ] + }) + req.flush({}) + }) + + + it('should transform response properly', () => { + + const service = TestBed.inject(InterSpaceCoordXformSvc) + const httpTestingController = TestBed.inject(HttpTestingController) + + service.transform( + VALID_TEMPLATE_SPACE_NAMES.MNI152, + VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, + [1,2,3] + ).subscribe(({ status, result }) => { + expect(status).toEqual('completed') + expect(result).toEqual([1e6, 2e6, 3e6]) + }) + const req = httpTestingController.expectOne(service['url']) + req.flush({ + 'target_points':[ + [1, 2, 3] + ] + }) + }) + + it('if server returns >=400, fallback gracefully', done => { + const service = TestBed.inject(InterSpaceCoordXformSvc) + const httpTestingController = TestBed.inject(HttpTestingController) + + service.transform( + VALID_TEMPLATE_SPACE_NAMES.MNI152, + VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, + [1,2,3] + ).subscribe(({ status }) => { + expect(status).toEqual('error') + done() + }) + const req = httpTestingController.expectOne(service['url']) + + req.flush('intercepted', { status: 500, statusText: 'internal server error' }) + }) + + it('if server does not respond after 3s, fallback gracefully', fakeAsync(() => { + + const service = TestBed.inject(InterSpaceCoordXformSvc) + const httpTestingController = TestBed.inject(HttpTestingController) + + service.transform( + VALID_TEMPLATE_SPACE_NAMES.MNI152, + VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, + [1,2,3] + ).subscribe(({ status, statusText }) => { + expect(status).toEqual('error') + expect(statusText).toEqual(`Timeout after 3s`) + }) + const req = httpTestingController.expectOne(service['url']) + tick(4000) + expect(req.cancelled).toBe(true) + })) + }) + }) +}) diff --git a/src/atlasComponents/sapi/core/space/interSpaceCoordXform.service.ts b/src/atlasComponents/sapi/core/space/interSpaceCoordXform.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a0c0c690be0eb7615922a8068ffc86422af54fb --- /dev/null +++ b/src/atlasComponents/sapi/core/space/interSpaceCoordXform.service.ts @@ -0,0 +1,107 @@ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders, HttpErrorResponse } from "@angular/common/http"; +import { catchError, timeout, map } from "rxjs/operators"; +import { of, Observable } from "rxjs"; +import { environment } from 'src/environments/environment' +import { IDS } from "src/atlasComponents/sapi/constants" + +type ITemplateCoordXformResp = { + status: 'pending' | 'error' | 'completed' | 'cached' + statusText?: string + result? : [number, number, number] +} + +export const VALID_TEMPLATE_SPACE_NAMES = { + MNI152: 'MNI 152 ICBM 2009c Nonlinear Asymmetric', + COLIN27: 'MNI Colin 27', + BIG_BRAIN: 'Big Brain (Histology)', + INFANT: 'Infant Atlas', +} as const + +export type ValidTemplateSpaceName = typeof VALID_TEMPLATE_SPACE_NAMES[keyof typeof VALID_TEMPLATE_SPACE_NAMES] + +@Injectable({ + providedIn: 'root', +}) +export class InterSpaceCoordXformSvc { + + static TmplIdToValidSpaceName(tmplId: string): ValidTemplateSpaceName{ + switch (tmplId) { + case IDS.TEMPLATES.BIG_BRAIN: return VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN + case IDS.TEMPLATES.MNI152: return VALID_TEMPLATE_SPACE_NAMES.MNI152 + case IDS.TEMPLATES.COLIN27: return VALID_TEMPLATE_SPACE_NAMES.COLIN27 + default: return null + } + } + + private cache = { + _map: new Map<string, [number, number, number]>(), + _getKey(srcTmplName: ValidTemplateSpaceName, targetTmplName: ValidTemplateSpaceName, coordinatesInNm: [number, number, number]) { + return `${srcTmplName}:${targetTmplName}:${coordinatesInNm.map(v => Math.round(v / 1e3)).join(',')}` + }, + set(srcTmplName: ValidTemplateSpaceName, targetTmplName: ValidTemplateSpaceName, coordinatesInNm: [number, number, number], result: [number, number, number]) { + const key = this._getKey(srcTmplName, targetTmplName, coordinatesInNm) + return this._map.set(key, result) + }, + get(srcTmplName: ValidTemplateSpaceName, targetTmplName: ValidTemplateSpaceName, coordinatesInNm: [number, number, number]) { + const key = this._getKey(srcTmplName, targetTmplName, coordinatesInNm) + return this._map.get(key) + } + } + + constructor(private httpClient: HttpClient) {} + + private url = `${environment.SPATIAL_TRANSFORM_BACKEND.replace(/\/$/, '')}/v1/transform-points` + + // jasmine marble cannot test promise properly + // see https://github.com/ngrx/platform/issues/498#issuecomment-337465179 + // in order to properly test with marble, use obs instead of promise + transform(srcTmplName: ValidTemplateSpaceName, targetTmplName: ValidTemplateSpaceName, coordinatesInNm: [number, number, number]): Observable<ITemplateCoordXformResp> { + if (!srcTmplName || !targetTmplName) { + return of({ + status: 'error', + statusText: 'either srcTmplName or targetTmplName is undefined' + } as ITemplateCoordXformResp) + } + const cachedResult = this.cache.get(srcTmplName, targetTmplName, coordinatesInNm) + if (cachedResult) { + return of({ + status: 'cached', + result: cachedResult, + } as ITemplateCoordXformResp) + } + + const httpOptions = { + headers: new HttpHeaders({ + 'Content-Type': 'application/json', + }) + } + return this.httpClient.post( + this.url, + JSON.stringify({ + 'source_points': [[...coordinatesInNm.map(c => c/1e6)]], + 'source_space': srcTmplName, + 'target_space': targetTmplName + }), + httpOptions + ).pipe( + map(resp => { + const result = resp['target_points'][0].map((r: number)=> r * 1e6) as [number, number, number] + this.cache.set(srcTmplName, targetTmplName, coordinatesInNm, result) + return { + status: 'completed', + result + } as ITemplateCoordXformResp + }), + catchError(err => { + if (err instanceof HttpErrorResponse) { + return of(({ status: 'error', statusText: err.message } as ITemplateCoordXformResp)) + } else { + return of(({ status: 'error', statusText: err.toString() } as ITemplateCoordXformResp)) + } + }), + timeout(3000), + catchError(() => of(({ status: 'error', statusText: `Timeout after 3s` } as ITemplateCoordXformResp))), + ) + } +} \ No newline at end of file diff --git a/src/atlasComponents/sapi/features/index.ts b/src/atlasComponents/sapi/features/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..560300f7fbccdc5d69dbdb52be17c003f0bcfd25 --- /dev/null +++ b/src/atlasComponents/sapi/features/index.ts @@ -0,0 +1,3 @@ +export { + SAPIFeature +} from "./sapiFeature" \ No newline at end of file diff --git a/src/atlasComponents/sapi/features/sapiFeature.ts b/src/atlasComponents/sapi/features/sapiFeature.ts new file mode 100644 index 0000000000000000000000000000000000000000..5abaa0040f6cb71046c70bf57e7a77a57f2794a8 --- /dev/null +++ b/src/atlasComponents/sapi/features/sapiFeature.ts @@ -0,0 +1,13 @@ +import { SAPI } from "../sapi.service"; +import { SapiFeatureModel } from "../type"; + +export class SAPIFeature { + constructor(private sapi: SAPI, public id: string, public opts: Record<string, string> = {}){ + + } + + public detail$ = this.sapi.httpGet<SapiFeatureModel>( + `${SAPI.bsEndpoint}/features/${this.id}`, + this.opts + ) +} diff --git a/src/atlasComponents/sapi/index.ts b/src/atlasComponents/sapi/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e5d8afd20e4e298a52db0b0edfc3fa61c55a5e74 --- /dev/null +++ b/src/atlasComponents/sapi/index.ts @@ -0,0 +1,30 @@ +export { SAPIModule } from './module' + +export { + SapiAtlasModel, + SapiParcellationModel, + SapiSpaceModel, + SapiRegionModel, + SapiVolumeModel, + SapiDatasetModel, + SapiRegionalFeatureModel, + SapiSpatialFeatureModel, + SapiFeatureModel, + SapiParcellationFeatureModel, + CleanedIeegDataset, + SxplrCleanedFeatureModel, + OpenMINDSCoordinatePoint, + CLEANED_IEEG_DATASET_TYPE, +} from "./type" + +export { SAPI } from "./sapi.service" +export { + SAPIAtlas, + SAPISpace, + SAPIParcellation, + SAPIRegion +} from "./core" + +export { + IDS +} from "./constants" \ No newline at end of file diff --git a/src/atlasComponents/sapi/module.ts b/src/atlasComponents/sapi/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..a64cc8bc817f05c801cde40d58a95d93c1d198a1 --- /dev/null +++ b/src/atlasComponents/sapi/module.ts @@ -0,0 +1,27 @@ +import { NgModule } from "@angular/core"; +import { SAPI } from "./sapi.service"; +import { CommonModule } from "@angular/common"; +import { HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http"; +import { PriorityHttpInterceptor } from "src/util/priority"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + MatSnackBarModule, + ], + declarations: [ + ], + exports: [ + ], + providers: [ + SAPI, + { + provide: HTTP_INTERCEPTORS, + useClass: PriorityHttpInterceptor, + multi: true + } + ] +}) +export class SAPIModule{} diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..62ba0e2528a8633db52bc44acdcadd3e9e851e04 --- /dev/null +++ b/src/atlasComponents/sapi/sapi.service.ts @@ -0,0 +1,259 @@ +import { Injectable } from "@angular/core"; +import { HttpClient } from '@angular/common/http'; +import { map, shareReplay } from "rxjs/operators"; +import { SAPIAtlas, SAPISpace } from './core' +import { + SapiAtlasModel, SapiModalityModel, + SapiParcellationModel, + SapiQueryPriorityArg, + SapiRegionalFeatureModel, + SapiRegionModel, + SapiSpaceModel, + SpyNpArrayDataModel, + SxplrCleanedFeatureModel +} from "./type"; +import { getExportNehuba } from "src/util/fn"; +import { SAPIParcellation } from "./core/sapiParcellation"; +import { SAPIRegion } from "./core/sapiRegion" +import { MatSnackBar } from "@angular/material/snack-bar"; +import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; +import { EnumColorMapName } from "src/util/colorMaps"; +import { PRIORITY_HEADER } from "src/util/priority"; +import { Observable } from "rxjs"; +import { SAPIFeature } from "./features"; +import { environment } from "src/environments/environment" + +export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version' +export const SIIBRA_API_VERSION = '0.2.0' + +type RegistryType = SAPIAtlas | SAPISpace | SAPIParcellation + +@Injectable() +export class SAPI{ + static bsEndpoint = environment.BS_REST_URL || `https://siibra-api-latest.apps-dev.hbp.eu/v2_0` + + public bsEndpoint = SAPI.bsEndpoint + + registry = { + _map: {} as Record<string, { + func: (...arg: any[]) => RegistryType + args: string[] + }>, + get<T>(id: string): T { + if (!this._map[id]) return null + const { func, args } = this._map[id] + return func(...args) + }, + set(id: string, func: (...args: any[]) => RegistryType, args: string[]) { + if (this._map[id]) { + console.warn(`id ${id} already mapped as ${this._map[id]}`) + } + this._map[id] = { func, args } + } + } + + getAtlas(atlasId: string): SAPIAtlas { + return new SAPIAtlas(this, atlasId) + } + + getSpace(atlasId: string, spaceId: string): SAPISpace { + return new SAPISpace(this, atlasId, spaceId) + } + + getParcellation(atlasId: string, parcId: string): SAPIParcellation { + return new SAPIParcellation(this, atlasId, parcId) + } + + getRegion(atlasId: string, parcId: string, regionId: string): SAPIRegion{ + return new SAPIRegion(this, atlasId, parcId, regionId) + } + + getSpaceDetail(atlasId: string, spaceId: string, param?: SapiQueryPriorityArg): Observable<SapiSpaceModel> { + return this.getSpace(atlasId, spaceId).getDetail(param) + } + + getParcDetail(atlasId: string, parcId: string, param?: SapiQueryPriorityArg): Observable<SapiParcellationModel> { + return this.getParcellation(atlasId, parcId).getDetail(param) + } + + getParcRegions(atlasId: string, parcId: string, spaceId: string, queryParam?: SapiQueryPriorityArg): Observable<SapiRegionModel[]> { + const parc = this.getParcellation(atlasId, parcId) + return parc.getRegions(spaceId, queryParam) + } + + getFeature(featureId: string, opts: Record<string, string> = {}) { + return new SAPIFeature(this, featureId, opts) + } + + getRegionFeatures(atlasId: string, parcId: string, spaceId: string, regionId: string, priority = 0): Observable<(SapiRegionalFeatureModel | SxplrCleanedFeatureModel)[]>{ + + const reg = this.getRegion(atlasId, parcId, regionId) + return reg.getFeatures(spaceId) + } + + getModalities(): Observable<SapiModalityModel[]> { + return this.http.get<SapiModalityModel[]>(`${SAPI.bsEndpoint}/modalities`) + } + + httpGet<T>(url: string, params?: Record<string, string>, sapiParam?: SapiQueryPriorityArg){ + const headers: Record<string, string> = {} + if (sapiParam?.priority) { + headers[PRIORITY_HEADER] = sapiParam.priority.toString() + } + return this.http.get<T>( + url, + { + headers, + params + } + ) + } + + public atlases$ = this.http.get<SapiAtlasModel[]>( + `${this.bsEndpoint}/atlases`, + { + observe: "response" + } + ).pipe( + map(resp => { + const respVersion = resp.headers.get(SIIBRA_API_VERSION_HEADER_KEY) + if (respVersion !== SIIBRA_API_VERSION) { + this.snackbar.open(`Expecting ${SIIBRA_API_VERSION}, got ${respVersion}. Some functionalities may not work as expected.`, 'Dismiss', { + duration: 5000 + }) + } + console.log(`siibra-api::version::${respVersion}, expecting::${SIIBRA_API_VERSION}`) + return resp.body + }), + shareReplay(1) + ) + + constructor( + public http: HttpClient, + private snackbar: MatSnackBar, + private workerSvc: AtlasWorkerService, + ){ + this.atlases$.subscribe(atlases => { + for (const atlas of atlases) { + for (const space of atlas.spaces) { + this.registry.set(space["@id"], this.getSpace.bind(this), [atlas["@id"], space["@id"]]) + this.getSpaceDetail(atlas["@id"], space["@id"]) + } + for (const parc of atlas.parcellations) { + this.registry.set(parc["@id"], this.getParcellation.bind(this), [atlas["@id"], parc["@id"]]) + this.getParcDetail(atlas["@id"], parc["@id"]) + } + } + }) + } + + async processNpArrayData<T extends keyof ProcessTypedArrayResult>(input: SpyNpArrayDataModel, method: PARSE_TYPEDARRAY = PARSE_TYPEDARRAY.RAW_ARRAY, params: ProcessTypedArrayResult[T]['input'] = null): Promise<ProcessTypedArrayResult[T]['output']> { + + const supportedDtype = [ + "uint8", + "int32", + "float32" + ] + const { + "x-channel": channel, + "x-width": width, + "x-height": height, + content, + dtype, + content_encoding: contentEncoding, + content_type: contentType + } = input + + if (contentType !== "application/octet-stream") { + throw new Error(`sapi.service#decodeNpArrayDataModel error: expecting content_type to be "application/octet-stream", but is ${contentType}`) + } + if (contentEncoding !== "gzip; base64") { + throw new Error(`sapi.service#decodeNpArrayDataModel error: expecting content_encoding to be "gzip; base64", but is ${contentEncoding}`) + } + if (supportedDtype.indexOf(dtype) < 0) { + throw new Error(`sapi.service#decodeNpArrayDataModel error: expecting dtype to be in ${JSON.stringify(supportedDtype)}, but is ${dtype}`) + } + + try { + const bin = atob(content) + const { pako } = getExportNehuba() + const array = pako.inflate(bin) + let workerMsg: string + switch (method) { + case PARSE_TYPEDARRAY.CANVAS_FORTRAN_RGBA: { + workerMsg = "PROCESS_TYPED_ARRAY_F2RGBA" + break + } + case PARSE_TYPEDARRAY.CANVAS_COLORMAP_RGBA: { + workerMsg = "PROCESS_TYPED_ARRAY_CM2RGBA" + break + } + case PARSE_TYPEDARRAY.RAW_ARRAY: { + workerMsg = "PROCESS_TYPED_ARRAY_RAW" + break + } + default:{ + throw new Error(`sapi.service#decodeNpArrayDataModel: method cannot be deciphered: ${method}`) + } + } + const { result } = await this.workerSvc.sendMessage({ + method: workerMsg, + param: { + inputArray: array, + width, + height, + channel, + dtype, + processParams: params + }, + transfers: [ array.buffer ] + }) + const { buffer, outputArray, min, max } = result + return { + type: method, + result: buffer, + rawArray: outputArray, + min, + max + } + } catch (e) { + throw new Error(`sapi.service#decodeNpArrayDataModel error: ${e}`) + } + } +} + +export enum PARSE_TYPEDARRAY { + CANVAS_FORTRAN_RGBA="CANVAS_FORTRAN_RGBA", + CANVAS_COLORMAP_RGBA="CANVAS_COLORMAP_RGBA", + RAW_ARRAY="RAW_ARRAY", +} + +type ProcessTypedArrayResult = { + [PARSE_TYPEDARRAY.CANVAS_FORTRAN_RGBA]: { + input: null + output: { + type: PARSE_TYPEDARRAY + result: Uint8ClampedArray + } + } + [PARSE_TYPEDARRAY.CANVAS_COLORMAP_RGBA]: { + input?: { + colormap?: EnumColorMapName + log?: boolean + } + output: { + type: PARSE_TYPEDARRAY + result: Uint8ClampedArray + max: number + min: number + } + } + [PARSE_TYPEDARRAY.RAW_ARRAY]: { + input: null + output: { + rawArray: number[][] + min: number + max: number + } + } +} diff --git a/src/atlasComponents/sapi/schema.ts b/src/atlasComponents/sapi/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..a54de84decb2dbb61346f6fc6182706d4542213c --- /dev/null +++ b/src/atlasComponents/sapi/schema.ts @@ -0,0 +1,1993 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/atlases/{atlas_id}/parcellations/{parcellation_id}/regions": { + /** Returns all regions for a given parcellation id. */ + get: operations["get_all_regions_from_atlas_parc_space_atlases__atlas_id__parcellations__parcellation_id__regions_get"] + } + "/atlases/{atlas_id}/parcellations/{parcellation_id}/regions/{region_id}/features": { + /** Returns all regional features for a region. */ + get: operations["get_all_regional_features_for_region_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__features_get"] + } + "/atlases/{atlas_id}/parcellations/{parcellation_id}/regions/{region_id}/features/{feature_id}": { + /** Returns a feature for a region, as defined by by the modality and feature ID */ + get: operations["get_single_detailed_regional_feature_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__features__feature_id__get"] + } + "/atlases/{atlas_id}/parcellations/{parcellation_id}/regions/{region_id}/regional_map/info": { + /** Returns information about a regional map for given region name. */ + get: operations["get_regional_map_info_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__regional_map_info_get"] + } + "/atlases/{atlas_id}/parcellations/{parcellation_id}/regions/{region_id}/regional_map/map": { + /** Returns a regional map for given region name. */ + get: operations["get_regional_map_file_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__regional_map_map_get"] + } + "/atlases/{atlas_id}/parcellations/{parcellation_id}/regions/{region_id}/volumes": { + get: operations["get_regional_volumes_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__volumes_get"] + } + "/atlases/{atlas_id}/parcellations/{parcellation_id}/regions/{region_id}": { + get: operations["get_single_region_detail_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__get"] + } + "/atlases/{atlas_id}/parcellations": { + /** Returns all parcellations that are defined in the siibra client for given atlas. */ + get: operations["get_all_parcellations_atlases__atlas_id__parcellations_get"] + } + "/atlases/{atlas_id}/parcellations/{parcellation_id}/features/{feature_id}": { + /** Returns a global feature for a specific modality id. */ + get: operations["get_single_detailed_global_feature_atlases__atlas_id__parcellations__parcellation_id__features__feature_id__get"] + } + "/atlases/{atlas_id}/parcellations/{parcellation_id}/features": { + /** Returns all global features for a parcellation. */ + get: operations["get_all_global_features_for_parcellation_atlases__atlas_id__parcellations__parcellation_id__features_get"] + } + "/atlases/{atlas_id}/parcellations/{parcellation_id}/volumes": { + /** Returns one parcellation for given id. */ + get: operations["get_volumes_for_parcellation_atlases__atlas_id__parcellations__parcellation_id__volumes_get"] + } + "/atlases/{atlas_id}/parcellations/{parcellation_id}": { + /** Returns one parcellation for given id. */ + get: operations["get_single_parcellation_detail_atlases__atlas_id__parcellations__parcellation_id__get"] + } + "/atlases/{atlas_id}/spaces": { + /** Returns all spaces that are defined in the siibra client. */ + get: operations["get_all_spaces_atlases__atlas_id__spaces_get"] + } + "/atlases/{atlas_id}/spaces/{space_id}/templates": { + /** Returns a template for a given space id. */ + get: operations["get_template_by_space_id_atlases__atlas_id__spaces__space_id__templates_get"] + } + "/atlases/{atlas_id}/spaces/{space_id}/parcellation_maps": { + /** Returns all parcellation maps for a given space id. */ + get: operations["get_parcellation_map_for_space_atlases__atlas_id__spaces__space_id__parcellation_maps_get"] + } + "/atlases/{atlas_id}/spaces/{space_id}/features/{feature_id}": { + /** + * Get a detailed view on a single spatial feature. + * A parcellation id and region id can be provided optional to get more details. + */ + get: operations["get_single_detailed_spatial_feature_atlases__atlas_id__spaces__space_id__features__feature_id__get"] + } + "/atlases/{atlas_id}/spaces/{space_id}/features": { + /** Return all possible feature names and links to get more details */ + get: operations["get_all_spatial_features_for_space_atlases__atlas_id__spaces__space_id__features_get"] + } + "/atlases/{atlas_id}/spaces/{space_id}/volumes": { + get: operations["get_volumes_for_space_atlases__atlas_id__spaces__space_id__volumes_get"] + } + "/atlases/{atlas_id}/spaces/{space_id}": { + /** Returns one space for given id, with links to further resources */ + get: operations["get_single_space_detail_atlases__atlas_id__spaces__space_id__get"] + } + "/atlases": { + /** Get all atlases known by siibra. */ + get: operations["get_all_atlases_atlases_get"] + } + "/atlases/{atlas_id}": { + /** Get more information for a specific atlas with links to further objects. */ + get: operations["get_atlas_by_id_atlases__atlas_id__get"] + } + "/genes": { + /** Return all genes (name, acronym) in siibra */ + get: operations["get_gene_names_genes_get"] + } + "/modalities": { + /** Return all possible modalities */ + get: operations["get_all_available_modalities_modalities_get"] + } + "/features/{feature_id}": { + /** + * Get all details for one feature by id. + * Since the feature id is unique, no atlas concept is required. + * + * Further optional params can extend the result. + * :param feature_id: + * :param atlas_id: + * :param space_id: + * :param parcellation_id: + * :param region_id: + * :return: FeatureModels + */ + get: operations["get_feature_details_features__feature_id__get"] + } +} + +export interface components { + schemas: { + /** AutoradiographyDataModel */ + AutoradiographyDataModel: { + /** + * Content Type + * @default application/octet-stream + */ + content_type?: string + /** + * Content Encoding + * @default gzip; base64 + */ + content_encoding?: string + /** X-Width */ + "x-width": number + /** X-Height */ + "x-height": number + /** X-Channel */ + "x-channel": number + /** Dtype */ + dtype: string + /** Content */ + content: string + } + /** AxesOrigin */ + AxesOrigin: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * typeOfUncertainty + * @description Distinct technique used to quantify the uncertainty of a measurement. + */ + typeOfUncertainty?: unknown + /** + * uncertainty + * @description Quantitative value range defining the uncertainty of a measurement. + */ + uncertainty?: number[] + /** + * unit + * @description Determinate quantity adopted as a standard of measurement. + */ + unit?: unknown + /** + * value + * @description Entry for a property. + */ + value: number + } + /** BaseDatasetJsonModel */ + BaseDatasetJsonModel: { + /** @Id */ + "@id": string + /** + * @Type + * @constant + */ + "@type"?: "https://openminds.ebrains.eu/core/DatasetVersion" + metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"] + /** Urls */ + urls: components["schemas"]["Url"][] + } + /** BestViewPoint */ + BestViewPoint: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * coordinateSpace + * @description Two or three dimensional geometric setting. + */ + coordinateSpace: unknown + /** + * Coordinates + * @description Structured information on a quantitative value. + */ + coordinates: components["schemas"]["siibra__openminds__SANDS__v3__atlas__parcellationEntityVersion__Coordinates"][] + } + /** BoundingBoxModel */ + BoundingBoxModel: { + /** @Type */ + "@type": string + /** Space */ + space: { [key: string]: string } + center: components["schemas"]["siibra__openminds__SANDS__v3__miscellaneous__coordinatePoint__Model"] + minpoint: components["schemas"]["siibra__openminds__SANDS__v3__miscellaneous__coordinatePoint__Model"] + maxpoint: components["schemas"]["siibra__openminds__SANDS__v3__miscellaneous__coordinatePoint__Model"] + /** Shape */ + shape: number[] + /** Isplanar */ + isPlanar: boolean + } + /** ConnectivityMatrixDataModel */ + ConnectivityMatrixDataModel: { + /** @Id */ + "@id": string + /** @Type */ + "@type": string + /** Name */ + name: string + /** Parcellations */ + parcellations: { [key: string]: string }[] + matrix?: components["schemas"]["NpArrayDataModel"] + /** Columns */ + columns?: string[] + } + /** CorticalCellDistributionModel */ + CorticalCellDistributionModel: { + /** @Id */ + "@id": string + /** + * @Type + * @constant + */ + "@type"?: "siibra/features/cells" + metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"] + /** Urls */ + urls: components["schemas"]["Url"][] + /** Cells */ + cells?: components["schemas"]["CorticalCellModel"][] + /** Section */ + section?: string + /** Patch */ + patch?: string + } + /** CorticalCellModel */ + CorticalCellModel: { + /** X */ + x: number + /** Y */ + y: number + /** Area */ + area: number + /** Layer */ + layer: number + /** Instance Label */ + "instance label": number + } + /** DatasetJsonModel */ + DatasetJsonModel: { + /** @Id */ + "@id": string + /** + * @Type + * @constant + */ + "@type"?: "https://openminds.ebrains.eu/core/DatasetVersion" + metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"] + /** Urls */ + urls: components["schemas"]["Url"][] + } + /** FingerPrintDataModel */ + FingerPrintDataModel: { + /** Mean */ + mean: number + /** Std */ + std: number + /** Unit */ + unit: string + } + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][] + } + /** HasAnnotation */ + HasAnnotation: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * Bestviewpoint + * @description Structured information on a coordinate point. + */ + bestViewPoint?: components["schemas"]["BestViewPoint"] + /** + * criteria + * @description Aspects or standards on which a judgement or decision is based. + */ + criteria?: unknown + /** + * criteriaQualityType + * @description Distinct class that defines how the judgement or decision was made for a particular criteria. + */ + criteriaQualityType: unknown + /** + * displayColor + * @description Preferred coloring. + */ + displayColor?: string + /** + * inspiredBy + * @description Reference to an inspiring element. + */ + inspiredBy?: unknown[] + /** + * internalIdentifier + * @description Term or code that identifies someone or something within a particular product. + */ + internalIdentifier: string + /** + * laterality + * @description Differentiation between a pair of lateral homologous parts of the body. + */ + laterality?: unknown[] + /** + * visualizedIn + * @description Reference to an image in which something is visible. + */ + visualizedIn?: unknown + } + /** HasTerminologyVersion */ + HasTerminologyVersion: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * definedIn + * @description Reference to a file instance in which something is stored. + */ + definedIn?: unknown[] + /** hasEntityVersion */ + hasEntityVersion: unknown[] + /** + * ontologyIdentifier + * @description Term or code used to identify something or someone registered within a particular ontology. + */ + ontologyIdentifier?: string[] + } + /** HrefModel */ + HrefModel: { + /** Href */ + href: string + } + /** IEEGContactPointModel */ + IEEGContactPointModel: { + /** Inroi */ + inRoi?: boolean + /** Id */ + id: string + point: components["schemas"]["siibra__openminds__SANDS__v3__miscellaneous__coordinatePoint__Model"] + } + /** IEEGElectrodeModel */ + IEEGElectrodeModel: { + /** Inroi */ + inRoi?: boolean + /** Electrode Id */ + electrode_id: string + /** Contact Points */ + contact_points: { + [key: string]: components["schemas"]["IEEGContactPointModel"] + } + } + /** IEEGSessionModel */ + IEEGSessionModel: { + /** Inroi */ + inRoi?: boolean + /** @Id */ + "@id": string + /** + * @Type + * @constant + */ + "@type"?: "siibra/features/ieegSession" + dataset: components["schemas"]["DatasetJsonModel"] + /** Sub Id */ + sub_id: string + /** Electrodes */ + electrodes: { + [key: string]: components["schemas"]["IEEGElectrodeModel"] + } + } + /** NeurotransmitterMarkupModel */ + NeurotransmitterMarkupModel: { + /** Latex */ + latex: string + /** Markdown */ + markdown: string + /** Name */ + name: string + /** Label */ + label: string + } + /** NiiMetadataModel */ + NiiMetadataModel: { + /** Min */ + min: number + /** Max */ + max: number + } + /** NpArrayDataModel */ + NpArrayDataModel: { + /** + * Content Type + * @default application/octet-stream + */ + content_type?: string + /** + * Content Encoding + * @default gzip; base64 + */ + content_encoding?: string + /** X-Width */ + "x-width": number + /** X-Height */ + "x-height": number + /** X-Channel */ + "x-channel": number + /** Dtype */ + dtype: string + /** Content */ + content: string + } + /** Page[Union[siibra.features.connectivity.ConnectivityMatrixDataModel, app.models.SerializationErrorModel]] */ + "Page_Union_siibra.features.connectivity.ConnectivityMatrixDataModel__app.models.SerializationErrorModel__": { + /** Items */ + items: (Partial<components["schemas"]["ConnectivityMatrixDataModel"]> & + Partial<components["schemas"]["SerializationErrorModel"]>)[] + /** Total */ + total: number + /** Page */ + page: number + /** Size */ + size: number + } + /** Page[VolumeModel] */ + Page_VolumeModel_: { + /** Items */ + items: components["schemas"]["VolumeModel"][] + /** Total */ + total: number + /** Page */ + page: number + /** Size */ + size: number + } + /** ProfileDataModel */ + ProfileDataModel: { + density: components["schemas"]["NpArrayDataModel"] + /** Unit */ + unit: string + } + /** QuantitativeOverlapItem */ + QuantitativeOverlapItem: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * typeOfUncertainty + * @description Distinct technique used to quantify the uncertainty of a measurement. + */ + typeOfUncertainty?: unknown + /** + * uncertainty + * @description Quantitative value range defining the uncertainty of a measurement. + */ + uncertainty?: number[] + /** + * unit + * @description Determinate quantity adopted as a standard of measurement. + */ + unit?: unknown + /** + * value + * @description Entry for a property. + */ + value: number + } + /** QuantitativeOverlapItem1 */ + QuantitativeOverlapItem1: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * maxValue + * @description Greatest quantity attained or allowed. + */ + maxValue: number + /** maxValueUnit */ + maxValueUnit?: unknown + /** + * minValue + * @description Smallest quantity attained or allowed. + */ + minValue: number + /** minValueUnit */ + minValueUnit?: unknown + } + /** ReceptorDataModel */ + ReceptorDataModel: { + /** Autoradiographs */ + autoradiographs: { + [key: string]: components["schemas"]["AutoradiographyDataModel"] + } + /** Profiles */ + profiles: { [key: string]: components["schemas"]["ProfileDataModel"] } + /** Fingerprints */ + fingerprints: { + [key: string]: components["schemas"]["FingerPrintDataModel"] + } + /** Receptor Symbols */ + receptor_symbols: { + [key: string]: components["schemas"]["SymbolMarkupClass"] + } + } + /** ReceptorDatasetModel */ + ReceptorDatasetModel: { + /** @Id */ + "@id": string + /** + * @Type + * @constant + */ + "@type"?: "siibra/features/receptor" + metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"] + /** Urls */ + urls: components["schemas"]["Url"][] + data?: components["schemas"]["ReceptorDataModel"] + } + /** ReceptorMarkupModel */ + ReceptorMarkupModel: { + /** Latex */ + latex: string + /** Markdown */ + markdown: string + /** Name */ + name: string + } + /** RelationAssessmentItem */ + RelationAssessmentItem: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * criteria + * @description Aspects or standards on which a judgement or decision is based. + */ + criteria?: unknown + /** + * inRelationTo + * @description Reference to a related element. + */ + inRelationTo: unknown + /** + * qualitativeOverlap + * @description Semantic characterization of how much two things occupy the same space. + */ + qualitativeOverlap: unknown + } + /** RelationAssessmentItem1 */ + RelationAssessmentItem1: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * criteria + * @description Aspects or standards on which a judgement or decision is based. + */ + criteria?: unknown + /** + * inRelationTo + * @description Reference to a related element. + */ + inRelationTo: unknown + /** Quantitativeoverlap */ + quantitativeOverlap: Partial< + components["schemas"]["QuantitativeOverlapItem"] + > & + Partial<components["schemas"]["QuantitativeOverlapItem1"]> + } + /** SapiAtlasModel */ + SapiAtlasModel: { + /** Links */ + links: { [key: string]: components["schemas"]["HrefModel"] } + /** @Id */ + "@id": string + /** Name */ + name: string + /** + * @Type + * @constant + */ + "@type"?: "juelich/iav/atlas/v1.0.0" + /** Spaces */ + spaces: components["schemas"]["SiibraAtIdModel"][] + /** Parcellations */ + parcellations: components["schemas"]["SiibraAtIdModel"][] + species: components["schemas"]["SpeciesModel"] + } + /** SapiParcellationModel */ + SapiParcellationModel: { + /** Links */ + links: { [key: string]: components["schemas"]["HrefModel"] } + /** @Id */ + "@id": string + /** + * @Type + * @constant + */ + "@type"?: "minds/core/parcellationatlas/v1.0.0" + /** Name */ + name: string + /** Modality */ + modality?: string + /** Datasets */ + datasets: components["schemas"]["DatasetJsonModel"][] + /** Brainatlasversions */ + brainAtlasVersions: components["schemas"]["siibra__openminds__SANDS__v3__atlas__brainAtlasVersion__Model"][] + version?: components["schemas"]["SiibraParcellationVersionModel"] + } + /** SapiSpaceModel */ + SapiSpaceModel: { + /** Links */ + links: { [key: string]: components["schemas"]["HrefModel"] } + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * @Id + * @description Metadata node identifier. + */ + "@id": string + /** @Type */ + "@type": string + /** + * anatomicalAxesOrientation + * @description Relation between reference planes used in anatomy and mathematics. + */ + anatomicalAxesOrientation: { [key: string]: unknown } + /** + * Axesorigin + * @description Structured information on a quantitative value. + */ + axesOrigin: components["schemas"]["AxesOrigin"][] + /** + * defaultImage + * @description Two or three dimensional image that particluarly represents a specific coordinate space. + */ + defaultImage?: unknown[] + /** + * digitalIdentifier + * @description Digital handle to identify objects or legal persons. + */ + digitalIdentifier?: { [key: string]: unknown } + /** + * fullName + * @description Whole, non-abbreviated name of something or somebody. + */ + fullName: string + /** + * homepage + * @description Main website of something or someone. + */ + homepage?: { [key: string]: unknown } + /** + * howToCite + * @description Preferred format for citing a particular object or legal person. + */ + howToCite?: string + /** + * nativeUnit + * @description Determinate quantity used in the original measurement. + */ + nativeUnit: { [key: string]: unknown } + /** + * ontologyIdentifier + * @description Term or code used to identify something or someone registered within a particular ontology. + */ + ontologyIdentifier?: string[] + /** + * releaseDate + * Format: date + * @description Fixed date on which a product is due to become or was made available for the general public to see or buy + */ + releaseDate: string + /** + * shortName + * @description Shortened or fully abbreviated name of something or somebody. + */ + shortName: string + /** + * versionIdentifier + * @description Term or code used to identify the version of something. + */ + versionIdentifier: string + } + /** SerializationErrorModel */ + SerializationErrorModel: { + /** + * Type + * @constant + */ + type?: "spy/serialization-error" + /** Message */ + message: string + } + /** SiibraAtIdModel */ + SiibraAtIdModel: { + /** @Id */ + "@id": string + } + /** SiibraParcellationVersionModel */ + SiibraParcellationVersionModel: { + /** Name */ + name: string + /** Deprecated */ + deprecated?: boolean + prev?: components["schemas"]["SiibraAtIdModel"] + next?: components["schemas"]["SiibraAtIdModel"] + } + /** SpeciesModel */ + SpeciesModel: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * @Id + * @description Metadata node identifier. + */ + "@id": string + /** @Type */ + "@type": string + /** + * definition + * @description Short, but precise statement of the meaning of a word, word group, sign or a symbol. + */ + definition?: string + /** + * description + * @description Longer statement or account giving the characteristics of someone or something. + */ + description?: string + /** + * interlexIdentifier + * @description Persistent identifier for a term registered in the InterLex project. + */ + interlexIdentifier?: string + /** + * knowledgeSpaceLink + * @description Persistent link to an encyclopedia entry in the Knowledge Space project. + */ + knowledgeSpaceLink?: string + /** + * name + * @description Word or phrase that constitutes the distinctive designation of a being or thing. + */ + name: string + /** + * preferredOntologyIdentifier + * @description Persistent identifier of a preferred ontological term. + */ + preferredOntologyIdentifier?: string + /** + * synonym + * @description Words or expressions used in the same language that have the same or nearly the same meaning in some or all senses. + */ + synonym?: string[] + /** Kgv1Id */ + kgV1Id: string + } + /** SymbolMarkupClass */ + SymbolMarkupClass: { + receptor: components["schemas"]["ReceptorMarkupModel"] + neurotransmitter: components["schemas"]["NeurotransmitterMarkupModel"] + } + /** Url */ + Url: { + /** Doi */ + doi: string + /** Cite */ + cite?: string + } + /** VOIDataModel */ + VOIDataModel: { + /** @Id */ + "@id": string + /** + * @Type + * @constant + */ + "@type"?: "siibra/features/voi" + metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"] + /** Urls */ + urls: components["schemas"]["Url"][] + /** Volumes */ + volumes: components["schemas"]["VolumeModel"][] + location: components["schemas"]["BoundingBoxModel"] + } + /** ValidationError */ + ValidationError: { + /** Location */ + loc: (Partial<string> & Partial<number>)[] + /** Message */ + msg: string + /** Error Type */ + type: string + } + /** VocabModel */ + VocabModel: { + /** @Vocab */ + "@vocab": string + } + /** VolumeDataModel */ + VolumeDataModel: { + /** Type */ + type: string + /** Is Volume */ + is_volume: boolean + /** Is Surface */ + is_surface: boolean + /** Detail */ + detail: { [key: string]: unknown } + space: components["schemas"]["SiibraAtIdModel"] + /** Url */ + url?: string + /** Url Map */ + url_map?: { [key: string]: string } + /** Map Type */ + map_type?: string + /** Volume Type */ + volume_type?: string + } + /** VolumeModel */ + VolumeModel: { + /** @Id */ + "@id": string + /** @Type */ + "@type": string + metadata: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Model"] + /** Urls */ + urls: components["schemas"]["Url"][] + data: components["schemas"]["VolumeDataModel"] + } + /** Copyright */ + siibra__openminds__SANDS__v3__atlas__brainAtlasVersion__Copyright: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * holder + * @description Legal person in possession of something. + */ + holder: unknown[] + /** + * year + * @description Cycle in the Gregorian calendar specified by a number and comprised of 365 or 366 days divided into 12 months beginning with January and ending with December. + */ + year: string + } + /** Model */ + siibra__openminds__SANDS__v3__atlas__brainAtlasVersion__Model: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * @Id + * @description Metadata node identifier. + */ + "@id": string + /** @Type */ + "@type": string + /** abbreviation */ + abbreviation?: string + /** + * accessibility + * @description Level to which something is accessible to someone or something. + */ + accessibility: { [key: string]: unknown } + /** atlasType */ + atlasType?: { [key: string]: unknown } + /** + * author + * @description Creator of a literary or creative work, as well as a dataset publication. + */ + author?: unknown[] + /** + * coordinateSpace + * @description Two or three dimensional geometric setting. + */ + coordinateSpace: { [key: string]: unknown } + /** + * Copyright + * @description Structured information on the copyright. + */ + copyright?: components["schemas"]["siibra__openminds__SANDS__v3__atlas__brainAtlasVersion__Copyright"] + /** + * custodian + * @description The 'custodian' is a legal person who is responsible for the content and quality of the data, metadata, and/or code of a research product. + */ + custodian?: unknown[] + /** + * description + * @description Longer statement or account giving the characteristics of someone or something. + */ + description?: string + /** + * digitalIdentifier + * @description Digital handle to identify objects or legal persons. + */ + digitalIdentifier?: { [key: string]: unknown } + /** + * fullDocumentation + * @description Non-abridged instructions, comments, and information for using a particular product. + */ + fullDocumentation: { [key: string]: unknown } + /** + * fullName + * @description Whole, non-abbreviated name of something or somebody. + */ + fullName?: string + /** + * funding + * @description Money provided by a legal person for a particular purpose. + */ + funding?: unknown[] + hasTerminologyVersion: components["schemas"]["HasTerminologyVersion"] + /** + * homepage + * @description Main website of something or someone. + */ + homepage?: { [key: string]: unknown } + /** + * howToCite + * @description Preferred format for citing a particular object or legal person. + */ + howToCite?: string + /** + * isAlternativeVersionOf + * @description Reference to an original form where the essence was preserved, but presented in an alternative form. + */ + isAlternativeVersionOf?: unknown[] + /** + * isNewVersionOf + * @description Reference to a previous (potentially outdated) particular form of something. + */ + isNewVersionOf?: { [key: string]: unknown } + /** + * keyword + * @description Significant word or concept that are representative of something or someone. + */ + keyword?: unknown[] + /** + * license + * @description Grant by a party to another party as an element of an agreement between those parties that permits to do, use, or own something. + */ + license: { [key: string]: unknown } + /** + * ontologyIdentifier + * @description Term or code used to identify something or someone registered within a particular ontology. + */ + ontologyIdentifier?: string[] + /** + * Othercontribution + * @description Structured information on the contribution made to a research product. + */ + otherContribution?: components["schemas"]["siibra__openminds__SANDS__v3__atlas__brainAtlasVersion__OtherContribution"] + /** + * relatedPublication + * @description Reference to something that was made available for the general public to see or buy. + */ + relatedPublication?: unknown[] + /** + * releaseDate + * Format: date + * @description Fixed date on which a product is due to become or was made available for the general public to see or buy + */ + releaseDate: string + /** + * repository + * @description Place, room, or container where something is deposited or stored. + */ + repository?: { [key: string]: unknown } + /** + * shortName + * @description Shortened or fully abbreviated name of something or somebody. + */ + shortName: string + /** + * supportChannel + * @description Way of communication used to interact with users or customers. + */ + supportChannel?: string[] + /** + * versionIdentifier + * @description Term or code used to identify the version of something. + */ + versionIdentifier: string + /** + * versionInnovation + * @description Documentation on what changed in comparison to a previously published form of something. + */ + versionInnovation: string + } + /** OtherContribution */ + siibra__openminds__SANDS__v3__atlas__brainAtlasVersion__OtherContribution: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * contributionType + * @description Distinct class of what was given or supplied as a part or share. + */ + contributionType: unknown[] + /** + * contributor + * @description Legal person that gave or supplied something as a part or share. + */ + contributor: unknown + } + /** Coordinates */ + siibra__openminds__SANDS__v3__atlas__parcellationEntityVersion__Coordinates: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * typeOfUncertainty + * @description Distinct technique used to quantify the uncertainty of a measurement. + */ + typeOfUncertainty?: unknown + /** + * uncertainty + * @description Quantitative value range defining the uncertainty of a measurement. + */ + uncertainty?: number[] + /** + * unit + * @description Determinate quantity adopted as a standard of measurement. + */ + unit?: unknown + /** + * value + * @description Entry for a property. + */ + value: number + } + /** Model */ + siibra__openminds__SANDS__v3__atlas__parcellationEntityVersion__Model: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * @Id + * @description Metadata node identifier. + */ + "@id": string + /** @Type */ + "@type": string + hasAnnotation?: components["schemas"]["HasAnnotation"] + /** + * hasParent + * @description Reference to a parent object or legal person. + */ + hasParent?: unknown[] + /** lookupLabel */ + lookupLabel?: string + /** + * name + * @description Word or phrase that constitutes the distinctive designation of a being or thing. + */ + name?: string + /** + * ontologyIdentifier + * @description Term or code used to identify something or someone registered within a particular ontology. + */ + ontologyIdentifier?: string[] + /** Relationassessment */ + relationAssessment?: Partial< + components["schemas"]["RelationAssessmentItem"] + > & + Partial<components["schemas"]["RelationAssessmentItem1"]> + /** + * versionIdentifier + * @description Term or code used to identify the version of something. + */ + versionIdentifier: string + /** + * versionInnovation + * @description Documentation on what changed in comparison to a previously published form of something. + */ + versionInnovation?: string + } + /** Coordinates */ + siibra__openminds__SANDS__v3__miscellaneous__coordinatePoint__Coordinates: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * typeOfUncertainty + * @description Distinct technique used to quantify the uncertainty of a measurement. + */ + typeOfUncertainty?: unknown + /** + * uncertainty + * @description Quantitative value range defining the uncertainty of a measurement. + */ + uncertainty?: number[] + /** + * unit + * @description Determinate quantity adopted as a standard of measurement. + */ + unit?: unknown + /** + * value + * @description Entry for a property. + */ + value: number + } + /** Model */ + siibra__openminds__SANDS__v3__miscellaneous__coordinatePoint__Model: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * @Id + * @description Metadata node identifier. + */ + "@id": string + /** @Type */ + "@type": string + /** + * coordinateSpace + * @description Two or three dimensional geometric setting. + */ + coordinateSpace: { [key: string]: unknown } + /** + * Coordinates + * @description Structured information on a quantitative value. + */ + coordinates: components["schemas"]["siibra__openminds__SANDS__v3__miscellaneous__coordinatePoint__Coordinates"][] + } + /** Copyright */ + siibra__openminds__core__v4__products__datasetVersion__Copyright: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * holder + * @description Legal person in possession of something. + */ + holder: unknown[] + /** + * year + * @description Cycle in the Gregorian calendar specified by a number and comprised of 365 or 366 days divided into 12 months beginning with January and ending with December. + */ + year: string + } + /** Model */ + siibra__openminds__core__v4__products__datasetVersion__Model: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * @Id + * @description Metadata node identifier. + */ + "@id": string + /** @Type */ + "@type": string + /** + * accessibility + * @description Level to which something is accessible to someone or something. + */ + accessibility: { [key: string]: unknown } + /** + * author + * @description Creator of a literary or creative work, as well as a dataset publication. + */ + author?: unknown[] + /** behavioralProtocol */ + behavioralProtocol?: unknown[] + /** + * Copyright + * @description Structured information on the copyright. + */ + copyright?: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__Copyright"] + /** + * custodian + * @description The 'custodian' is a legal person who is responsible for the content and quality of the data, metadata, and/or code of a research product. + */ + custodian?: unknown[] + /** dataType */ + dataType: unknown[] + /** + * description + * @description Longer statement or account giving the characteristics of someone or something. + */ + description?: string + /** + * digitalIdentifier + * @description Digital handle to identify objects or legal persons. + */ + digitalIdentifier: { [key: string]: unknown } + /** + * ethicsAssessment + * @description Judgment about the applied principles of conduct governing an individual or a group. + */ + ethicsAssessment: { [key: string]: unknown } + /** experimentalApproach */ + experimentalApproach: unknown[] + /** + * fullDocumentation + * @description Non-abridged instructions, comments, and information for using a particular product. + */ + fullDocumentation: { [key: string]: unknown } + /** + * fullName + * @description Whole, non-abbreviated name of something or somebody. + */ + fullName?: string + /** + * funding + * @description Money provided by a legal person for a particular purpose. + */ + funding?: unknown[] + /** + * homepage + * @description Main website of something or someone. + */ + homepage?: { [key: string]: unknown } + /** + * howToCite + * @description Preferred format for citing a particular object or legal person. + */ + howToCite?: string + /** + * inputData + * @description Data that is put into a process or machine. + */ + inputData?: unknown[] + /** + * isAlternativeVersionOf + * @description Reference to an original form where the essence was preserved, but presented in an alternative form. + */ + isAlternativeVersionOf?: unknown[] + /** + * isNewVersionOf + * @description Reference to a previous (potentially outdated) particular form of something. + */ + isNewVersionOf?: { [key: string]: unknown } + /** + * keyword + * @description Significant word or concept that are representative of something or someone. + */ + keyword?: unknown[] + /** + * license + * @description Grant by a party to another party as an element of an agreement between those parties that permits to do, use, or own something. + */ + license: { [key: string]: unknown } + /** + * Othercontribution + * @description Structured information on the contribution made to a research product. + */ + otherContribution?: components["schemas"]["siibra__openminds__core__v4__products__datasetVersion__OtherContribution"] + /** preparationDesign */ + preparationDesign?: unknown[] + /** + * relatedPublication + * @description Reference to something that was made available for the general public to see or buy. + */ + relatedPublication?: unknown[] + /** + * releaseDate + * Format: date + * @description Fixed date on which a product is due to become or was made available for the general public to see or buy + */ + releaseDate: string + /** + * repository + * @description Place, room, or container where something is deposited or stored. + */ + repository?: { [key: string]: unknown } + /** + * shortName + * @description Shortened or fully abbreviated name of something or somebody. + */ + shortName: string + /** studiedSpecimen */ + studiedSpecimen?: unknown[] + /** + * studyTarget + * @description Structure or function that was targeted within a study. + */ + studyTarget?: unknown[] + /** + * supportChannel + * @description Way of communication used to interact with users or customers. + */ + supportChannel?: string[] + /** + * technique + * @description Method of accomplishing a desired aim. + */ + technique: unknown[] + /** + * versionIdentifier + * @description Term or code used to identify the version of something. + */ + versionIdentifier: string + /** + * versionInnovation + * @description Documentation on what changed in comparison to a previously published form of something. + */ + versionInnovation: string + } + /** OtherContribution */ + siibra__openminds__core__v4__products__datasetVersion__OtherContribution: { + /** + * @Context + * @default [object Object] + */ + "@context"?: components["schemas"]["VocabModel"] + /** + * contributionType + * @description Distinct class of what was given or supplied as a part or share. + */ + contributionType: unknown[] + /** + * contributor + * @description Legal person that gave or supplied something as a part or share. + */ + contributor: unknown + } + } +} + +export interface operations { + /** Returns all regions for a given parcellation id. */ + get_all_regions_from_atlas_parc_space_atlases__atlas_id__parcellations__parcellation_id__regions_get: { + parameters: { + path: { + atlas_id: string + parcellation_id: string + } + query: { + space_id?: string + find?: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["siibra__openminds__SANDS__v3__atlas__parcellationEntityVersion__Model"][] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns all regional features for a region. */ + get_all_regional_features_for_region_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__features_get: { + parameters: { + path: { + atlas_id: string + parcellation_id: string + region_id: string + } + query: { + space_id?: string + type?: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": (Partial< + components["schemas"]["ReceptorDatasetModel"] + > & + Partial<components["schemas"]["BaseDatasetJsonModel"]> & + Partial<components["schemas"]["CorticalCellDistributionModel"]>)[] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns a feature for a region, as defined by by the modality and feature ID */ + get_single_detailed_regional_feature_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__features__feature_id__get: { + parameters: { + path: { + atlas_id: string + parcellation_id: string + region_id: string + feature_id: string + } + query: { + space_id?: string + gene?: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": Partial< + components["schemas"]["ReceptorDatasetModel"] + > & + Partial<components["schemas"]["BaseDatasetJsonModel"]> & + Partial<components["schemas"]["CorticalCellDistributionModel"]> + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns information about a regional map for given region name. */ + get_regional_map_info_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__regional_map_info_get: { + parameters: { + path: { + atlas_id: string + parcellation_id: string + region_id: string + } + query: { + space_id?: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["NiiMetadataModel"] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns a regional map for given region name. */ + get_regional_map_file_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__regional_map_map_get: { + parameters: { + path: { + atlas_id: string + parcellation_id: string + region_id: string + } + query: { + space_id?: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": unknown + "application/octet-stream": string + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + get_regional_volumes_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__volumes_get: { + parameters: { + path: { + atlas_id: string + parcellation_id: string + region_id: string + } + query: { + space_id?: string + type?: string + page?: number + size?: number + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["Page_VolumeModel_"] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + get_single_region_detail_atlases__atlas_id__parcellations__parcellation_id__regions__region_id__get: { + parameters: { + path: { + atlas_id: string + parcellation_id: string + region_id: string + } + query: { + space_id?: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["siibra__openminds__SANDS__v3__atlas__parcellationEntityVersion__Model"] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns all parcellations that are defined in the siibra client for given atlas. */ + get_all_parcellations_atlases__atlas_id__parcellations_get: { + parameters: { + path: { + atlas_id: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["SapiParcellationModel"][] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns a global feature for a specific modality id. */ + get_single_detailed_global_feature_atlases__atlas_id__parcellations__parcellation_id__features__feature_id__get: { + parameters: { + path: { + atlas_id: string + parcellation_id: string + feature_id: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": Partial< + components["schemas"]["ConnectivityMatrixDataModel"] + > & + Partial<components["schemas"]["SerializationErrorModel"]> + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns all global features for a parcellation. */ + get_all_global_features_for_parcellation_atlases__atlas_id__parcellations__parcellation_id__features_get: { + parameters: { + path: { + atlas_id: string + parcellation_id: string + } + query: { + type?: string + page?: number + size?: number + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["Page_Union_siibra.features.connectivity.ConnectivityMatrixDataModel__app.models.SerializationErrorModel__"] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns one parcellation for given id. */ + get_volumes_for_parcellation_atlases__atlas_id__parcellations__parcellation_id__volumes_get: { + parameters: { + path: { + atlas_id: string + parcellation_id: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["VolumeModel"][] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns one parcellation for given id. */ + get_single_parcellation_detail_atlases__atlas_id__parcellations__parcellation_id__get: { + parameters: { + path: { + atlas_id: string + parcellation_id: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["SapiParcellationModel"] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns all spaces that are defined in the siibra client. */ + get_all_spaces_atlases__atlas_id__spaces_get: { + parameters: { + path: { + atlas_id: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["SapiSpaceModel"][] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns a template for a given space id. */ + get_template_by_space_id_atlases__atlas_id__spaces__space_id__templates_get: { + parameters: { + path: { + atlas_id: string + space_id: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": unknown + "application/octet-stream": string + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns all parcellation maps for a given space id. */ + get_parcellation_map_for_space_atlases__atlas_id__spaces__space_id__parcellation_maps_get: { + parameters: { + path: { + atlas_id: string + space_id: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": unknown + "application/octet-stream": string + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** + * Get a detailed view on a single spatial feature. + * A parcellation id and region id can be provided optional to get more details. + */ + get_single_detailed_spatial_feature_atlases__atlas_id__spaces__space_id__features__feature_id__get: { + parameters: { + path: { + feature_id: string + atlas_id: string + space_id: string + } + query: { + parcellation_id?: string + region?: string + bbox?: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": Partial< + components["schemas"]["IEEGSessionModel"] + > & + Partial<components["schemas"]["VOIDataModel"]> + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Return all possible feature names and links to get more details */ + get_all_spatial_features_for_space_atlases__atlas_id__spaces__space_id__features_get: { + parameters: { + path: { + atlas_id: string + space_id: string + } + query: { + parcellation_id?: string + region?: string + bbox?: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": (Partial< + components["schemas"]["IEEGSessionModel"] + > & + Partial<components["schemas"]["VOIDataModel"]>)[] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + get_volumes_for_space_atlases__atlas_id__spaces__space_id__volumes_get: { + parameters: { + path: { + atlas_id: string + space_id: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["VolumeModel"][] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Returns one space for given id, with links to further resources */ + get_single_space_detail_atlases__atlas_id__spaces__space_id__get: { + parameters: { + path: { + atlas_id: string + space_id: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["SapiSpaceModel"] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Get all atlases known by siibra. */ + get_all_atlases_atlases_get: { + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["SapiAtlasModel"][] + } + } + } + } + /** Get more information for a specific atlas with links to further objects. */ + get_atlas_by_id_atlases__atlas_id__get: { + parameters: { + path: { + atlas_id: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["SapiAtlasModel"] + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } + /** Return all genes (name, acronym) in siibra */ + get_gene_names_genes_get: { + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": unknown + } + } + } + } + /** Return all possible modalities */ + get_all_available_modalities_modalities_get: { + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": unknown + } + } + } + } + /** + * Get all details for one feature by id. + * Since the feature id is unique, no atlas concept is required. + * + * Further optional params can extend the result. + * :param feature_id: + * :param atlas_id: + * :param space_id: + * :param parcellation_id: + * :param region_id: + * :return: FeatureModels + */ + get_feature_details_features__feature_id__get: { + parameters: { + path: { + feature_id: string + } + query: { + atlas_id?: string + space_id?: string + parcellation_id?: string + region_id?: string + } + } + responses: { + /** Successful Response */ + 200: { + content: { + "application/json": Partial< + components["schemas"]["ReceptorDatasetModel"] + > & + Partial<components["schemas"]["BaseDatasetJsonModel"]> & + Partial<components["schemas"]["CorticalCellDistributionModel"]> & + Partial<components["schemas"]["IEEGSessionModel"]> & + Partial<components["schemas"]["VOIDataModel"]> & + Partial<components["schemas"]["ConnectivityMatrixDataModel"]> & + Partial<components["schemas"]["SerializationErrorModel"]> + } + } + /** Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"] + } + } + } + } +} + +export interface external {} diff --git a/src/atlasComponents/sapi/stories.base.ts b/src/atlasComponents/sapi/stories.base.ts new file mode 100644 index 0000000000000000000000000000000000000000..51fde4f0cd5695f6055398e8bd9ec39ed469c541 --- /dev/null +++ b/src/atlasComponents/sapi/stories.base.ts @@ -0,0 +1,151 @@ +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionalFeatureModel, SapiRegionModel, SapiSpaceModel } from "." +import { cleanIeegSessionDatasets, SapiParcellationFeatureModel, SapiSpatialFeatureModel, SxplrCleanedFeatureModel } from "./type" +import addons from '@storybook/addons'; +import { DARKTHEME } from "src/util/injectionTokens"; +import { APP_INITIALIZER, NgZone } from "@angular/core"; +import { DOCUMENT } from "@angular/common"; +import { BehaviorSubject } from "rxjs"; + +export function addAddonEventListener(eventName: string, callback: (...args: any[]) => void){ + const channel = addons.getChannel() + channel.on(eventName, callback) + return () => channel.off(eventName, callback) +} + +export const provideDarkTheme = [{ + provide: DARKTHEME, + useFactory: (zone: NgZone, document: Document) => { + const useDarkTheme = document.body.getAttribute('darktheme') === "true" + const sub = new BehaviorSubject(useDarkTheme) + addAddonEventListener("DARK_MODE", flag => { + zone.run(() => { + sub.next(flag) + }) + }) + return sub + }, + deps: [ NgZone, DOCUMENT ] +}, { + provide: APP_INITIALIZER, + multi: true, + useFactory: (document: Document) => { + addAddonEventListener("DARK_MODE", flag => { + document.body.setAttribute('darktheme', flag.toString()) + }) + return () => Promise.resolve() + }, + deps: [ DOCUMENT ] +}] + +export const atlasId = { + human: 'juelich/iav/atlas/v1.0.0/1', + rat: "minds/core/parcellationatlas/v1.0.0/522b368e-49a3-49fa-88d3-0870a307974a", + mouse: "juelich/iav/atlas/v1.0.0/2", + monkey: "juelich/iav/atlas/v1.0.0/monkey" +} + +export const spaceId = { + human: { + mni152: 'minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2', + bigbrain: 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588' + }, + rat: { + waxholm: "minds/core/referencespace/v1.0.0/d5717c4a-0fa1-46e6-918c-b8003069ade8" + } +} + +export const parcId = { + human: { + jba29: "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290", + longBundle: "juelich/iav/atlas/v1.0.0/5", + difumo256: "minds/core/parcellationatlas/v1.0.0/141d510f-0342-4f94-ace7-c97d5f160235", + corticalLayers: "juelich/iav/atlas/v1.0.0/3" + }, + rat: { + v4: 'minds/core/parcellationatlas/v1.0.0/ebb923ba-b4d5-4b82-8088-fa9215c2e1fe-v4' + } +} + +export async function getAtlases(): Promise<SapiAtlasModel[]> { + return await (await fetch(`${SAPI.bsEndpoint}/atlases`)).json() as SapiAtlasModel[] +} + +export async function getAtlas(id: string): Promise<SapiAtlasModel>{ + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${id}`)).json() +} + +export async function getParc(atlasId: string, id: string): Promise<SapiParcellationModel>{ + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId}/parcellations/${id}`)).json() +} +export async function getParcRegions(atlasId: string, id: string, spaceId: string): Promise<SapiRegionModel[]>{ + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId}/parcellations/${id}/regions?space_id=${encodeURIComponent(spaceId)}`)).json() +} + +export async function getSpace(atlasId: string, id: string): Promise<SapiSpaceModel> { + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId}/spaces/${id}`)).json() +} + +export async function getHumanAtlas(): Promise<SapiAtlasModel> { + return getAtlas(atlasId.human) +} + +export async function getMni152(): Promise<SapiSpaceModel> { + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}`)).json() +} + +export async function getJba29(): Promise<SapiParcellationModel> { + return await getParc(atlasId.human, parcId.human.jba29) +} + +export async function getJba29Regions(): Promise<SapiRegionModel[]> { + return await getParcRegions(atlasId.human, parcId.human.jba29, spaceId.human.mni152) +} + +export async function getHoc1Right(spaceId=null): Promise<SapiRegionModel> { + if (!spaceId) { + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right`)).json() + } + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right?space_id=${encodeURIComponent(spaceId)}`)).json() +} + +export async function get44Left(spaceId=null): Promise<SapiRegionModel> { + if (!spaceId) { + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left`)).json() + } + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left?space_id=${encodeURIComponent(spaceId)}`)).json() +} + +export async function getHoc1RightSpatialFeatures(): Promise<SxplrCleanedFeatureModel[]> { + const json: SapiSpatialFeatureModel[] = await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features?parcellation_id=2.9®ion=hoc1%20right`)).json() + return cleanIeegSessionDatasets(json.filter(it => it['@type'] === "siibra/features/ieegSession")) +} + +export async function getHoc1RightFeatures(): Promise<SapiRegionalFeatureModel[]> { + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features`)).json() +} + +export async function getHoc1RightFeatureDetail(featId: string): Promise<SapiRegionalFeatureModel>{ + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features/${encodeURIComponent(featId)}`)).json() +} + +export async function getJba29Features(): Promise<SapiParcellationFeatureModel[]> { + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/features`)).json() +} + +export async function getBigbrainSpatialFeatures(): Promise<SapiSpatialFeatureModel[]>{ + const bbox = [ + [-1000, -1000, -1000], + [1000, 1000, 1000] + ] + const url = new URL(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.bigbrain}/features`) + url.searchParams.set(`bbox`, JSON.stringify(bbox)) + return await (await fetch(url.toString())).json() +} + +export async function getMni152SpatialFeatureHoc1Right(): Promise<SapiSpatialFeatureModel[]>{ + + const url = new URL(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features`) + url.searchParams.set(`parcellation_id`, parcId.human.jba29) + url.searchParams.set("region", 'hoc1 right') + return await (await fetch(url.toString())).json() +} diff --git a/src/atlasComponents/sapi/type.ts b/src/atlasComponents/sapi/type.ts new file mode 100644 index 0000000000000000000000000000000000000000..905c0dee7f004cba521b96860e44c4da6e801d10 --- /dev/null +++ b/src/atlasComponents/sapi/type.ts @@ -0,0 +1,121 @@ +import { OperatorFunction } from "rxjs" +import { map } from "rxjs/operators" +import { components, operations, paths } from "./schema" + +export type IdName = { + id: string + name: string +} + +export type SapiModalityModel = { + name: string + types: string[] +} + +type Point = [number, number, number] + +export type BoundingBoxConcept = [Point, Point] + +export type SapiAtlasModel = components["schemas"]["SapiAtlasModel"] +export type SapiSpaceModel = components["schemas"]["SapiSpaceModel"] +export type SapiParcellationModel = components["schemas"]["SapiParcellationModel"] +export type SapiRegionModel = components["schemas"]["siibra__openminds__SANDS__v3__atlas__parcellationEntityVersion__Model"] +export type OpenMINDSCoordinatePoint = components['schemas']['siibra__openminds__SANDS__v3__miscellaneous__coordinatePoint__Model'] +export type SxplrCoordinatePointExtension = { + openminds: OpenMINDSCoordinatePoint + name: string + description: string + color: string + '@id': string // should match the id of opendminds specs +} + +export type SapiRegionMapInfoModel = components["schemas"]["NiiMetadataModel"] +export type SapiVOIDataResponse = components["schemas"]["VOIDataModel"] +export type SapiVolumeModel = components["schemas"]["VolumeModel"] +export type SapiDatasetModel = components["schemas"]["DatasetJsonModel"] +export type SpyNpArrayDataModel = components["schemas"]["NpArrayDataModel"] +export type SapiIeegSessionModel = components["schemas"]["IEEGSessionModel"] + +/** + * utility types + */ +type PathReturn<T extends keyof paths> = Required<paths[T]["get"]["responses"][200]["content"]["application/json"]> +export type PaginatedResponse<T> = { + items: T[] + total: number + page: number + size: number +} + +/** + * serialization error type + */ +export type SapiSerializationErrorModel = components["schemas"]["SerializationErrorModel"] + +/** + * datafeatures from operations + */ + +export type SapiRegionalFeatureModel = PathReturn<"/atlases/{atlas_id}/parcellations/{parcellation_id}/regions/{region_id}/features/{feature_id}"> +export type SapiParcellationFeatureModel = PathReturn<"/atlases/{atlas_id}/parcellations/{parcellation_id}/features/{feature_id}"> +export type SapiSpatialFeatureModel = PathReturn<"/atlases/{atlas_id}/spaces/{space_id}/features/{feature_id}"> + +export type SapiFeatureModel = SapiRegionalFeatureModel | SapiSpatialFeatureModel | SapiParcellationFeatureModel + +/** + * specific data features + */ + +export type SapiRegionalFeatureReceptorModel = components["schemas"]["ReceptorDatasetModel"] +export type SapiParcellationFeatureMatrixModel = components["schemas"]["ConnectivityMatrixDataModel"] + + +export const CLEANED_IEEG_DATASET_TYPE = 'sxplr/cleanedIeegDataset' +export type CleanedIeegDataset = Required< + Omit<SapiDatasetModel, "@type"> & { + '@type': 'sxplr/cleanedIeegDataset' + sessions: Record<string, Omit<SapiIeegSessionModel, "dataset">> + } +> + +export function cleanIeegSessionDatasets(ieegSessions: SapiIeegSessionModel[]): CleanedIeegDataset[]{ + const returnArr: CleanedIeegDataset[] = [] + for (const sess of ieegSessions) { + const { dataset, ...itemToAppend } = sess + const existing = returnArr.find(it => it["@id"] === dataset["@id"]) + if (!existing) { + returnArr.push({ + ...dataset, + '@type': CLEANED_IEEG_DATASET_TYPE, + sessions: { + [sess.sub_id]: itemToAppend + } + }) + continue + } + existing.sessions[sess.sub_id] = itemToAppend + } + return returnArr +} + +export type SxplrCleanedFeatureModel = CleanedIeegDataset + +export function guardPipe< + InputType, + GuardType extends InputType +>( + guardFn: (input: InputType) => input is GuardType +): OperatorFunction<InputType, GuardType> { + return src => src.pipe( + map(val => { + if (guardFn(val)) { + return val + } + throw new Error(`TypeGuard Error`) + }) + ) +} + +export type SapiQueryPriorityArg = { + priority: number +} diff --git a/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.component.ts b/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..815a4d45ed29cad974c1465e8a8cca5a0af6bcee --- /dev/null +++ b/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.component.ts @@ -0,0 +1,56 @@ +import { Component, OnDestroy } from "@angular/core"; +import { Store, select } from "@ngrx/store"; +import { Observable, Subscription } from "rxjs"; +import { ARIA_LABELS } from 'common/constants' +import { atlasSelection, generalActions } from "src/state" +import { SAPI, SapiAtlasModel } from "src/atlasComponents/sapi"; + +@Component({ + selector: 'sxplr-sapiviews-core-atlas-dropdown-selector', + templateUrl: './dropdownAtlasSelector.template.html', + styleUrls: [ + './dropdownAtlasSelector.style.css' + ] +}) + +export class SapiViewsCoreAtlasAtlasDropdownSelector implements OnDestroy{ + + private subs: Subscription[] = [] + private fetchedAtlases: SapiAtlasModel[] = [] + public fetchedAtlases$: Observable<SapiAtlasModel[]> = this.sapi.atlases$ + public selectedAtlas$: Observable<SapiAtlasModel> = this.store$.pipe( + select(atlasSelection.selectors.selectedAtlas) + ) + + public SELECT_ATLAS_ARIA_LABEL = ARIA_LABELS.SELECT_ATLAS + + constructor( + private store$: Store<any>, + private sapi: SAPI, + ){ + this.subs.push( + this.fetchedAtlases$.subscribe(val => this.fetchedAtlases = val) + ) + } + + ngOnDestroy(): void { + this.subs.pop().unsubscribe() + } + + handleChangeAtlas({ value }) { + const found = this.fetchedAtlases.find(atlas => atlas["@id"] === value) + if (found) { + this.store$.dispatch( + atlasSelection.actions.selectAtlas({ + atlas: found + }) + ) + } else { + this.store$.dispatch( + generalActions.generalActionError({ + message: `Atlas with id ${value} not found.` + }) + ) + } + } +} diff --git a/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.stories.ts b/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..027fa024eb8bf497fa6600558b59098bab837d54 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.stories.ts @@ -0,0 +1,49 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI } from "src/atlasComponents/sapi" +import { provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { SapiViewsCoreAtlasAtlasDropdownSelector } from "./dropdownAtlasSelector.component" +import { SapiViewsCoreAtlasModule } from "../module" +import { atlasSelection } from "src/state" +import { StoreModule } from "@ngrx/store" + +export default { + component: SapiViewsCoreAtlasAtlasDropdownSelector, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreAtlasModule, + + StoreModule.forRoot({ + [atlasSelection.nameSpace]: atlasSelection.reducer + }), + ], + providers: [ + SAPI, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<SapiViewsCoreAtlasAtlasDropdownSelector> = (args: SapiViewsCoreAtlasAtlasDropdownSelector, { parameters }) => { + /** + * TODO can't seem to hook in handlechangeAtlas action + * always results in maximum call stack reached + * perhaps related: https://github.com/storybookjs/storybook/issues/13238 + */ + return ({ + props: { + }, + }) +} + + +export const Default = Template.bind({}) +Default.args = { + +} \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.style.css b/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.style.css similarity index 100% rename from src/atlasComponents/regionalFeatures/bsFeatures/ieeg/ieegCmp/ieeg.style.css rename to src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.style.css diff --git a/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.template.html b/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.template.html similarity index 87% rename from src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.template.html rename to src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.template.html index 4b5ddc4b0309e62ff937c8e6e5ffefeadfc55a6a..f3922fea6c44412fc83137f6c3ed71db7efd26e7 100644 --- a/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.template.html +++ b/src/atlasComponents/sapiViews/core/atlas/dropdownAtlasSelector/dropdownAtlasSelector.template.html @@ -4,7 +4,7 @@ </mat-label> <mat-select [aria-label]="SELECT_ATLAS_ARIA_LABEL" - [value]="selectedAtlas$ | async | getProperty" + [value]="(selectedAtlas$ | async)?.['@id']" (selectionChange)="handleChangeAtlas($event)"> <mat-option *ngFor="let atlas of (fetchedAtlases$ | async)" diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegSummary/kgRegSummary.style.css b/src/atlasComponents/sapiViews/core/atlas/index.ts similarity index 100% rename from src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature/kgRegSummary/kgRegSummary.style.css rename to src/atlasComponents/sapiViews/core/atlas/index.ts diff --git a/src/atlasComponents/sapiViews/core/atlas/module.ts b/src/atlasComponents/sapiViews/core/atlas/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..691d41303c80c52229423586c9243d27fc419479 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/atlas/module.ts @@ -0,0 +1,35 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SpinnerModule } from "src/components/spinner"; +import { AngularMaterialModule } from "src/sharedModules"; +import { QuickTourModule } from "src/ui/quickTour"; +import { SapiViewsUtilModule } from "../../util"; +import { SapiViewsCoreParcellationModule } from "../parcellation"; +import { SapiViewsCoreSpaceModule } from "../space"; +import { SapiViewsCoreAtlasAtlasDropdownSelector } from "./dropdownAtlasSelector/dropdownAtlasSelector.component"; +import { SapiViewsCoreAtlasSplashScreen } from "./splashScreen/splashScreen.component"; +import { SapiViewsCoreAtlasAtlasTmplParcSelector } from "./tmplParcSelector/tmplParcSelector.component"; + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule, + SapiViewsCoreSpaceModule, + SapiViewsCoreParcellationModule, + QuickTourModule, + SpinnerModule, + SapiViewsUtilModule, + ], + declarations: [ + SapiViewsCoreAtlasAtlasDropdownSelector, + SapiViewsCoreAtlasAtlasTmplParcSelector, + SapiViewsCoreAtlasSplashScreen, + ], + exports: [ + SapiViewsCoreAtlasAtlasDropdownSelector, + SapiViewsCoreAtlasAtlasTmplParcSelector, + SapiViewsCoreAtlasSplashScreen, + ] +}) + +export class SapiViewsCoreAtlasModule{} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/atlas/splashScreen/splashScreen.component.ts b/src/atlasComponents/sapiViews/core/atlas/splashScreen/splashScreen.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..056e3660c4243ee41bff9e5090354bd6e6fb2deb --- /dev/null +++ b/src/atlasComponents/sapiViews/core/atlas/splashScreen/splashScreen.component.ts @@ -0,0 +1,38 @@ +import { ChangeDetectionStrategy, Component } from "@angular/core"; +import { Store } from "@ngrx/store"; +import { tap } from 'rxjs/operators' +import { SAPI } from "src/atlasComponents/sapi/sapi.service"; +import { SapiAtlasModel } from "src/atlasComponents/sapi/type"; +import { atlasSelection } from "src/state" + +@Component({ + selector : 'ui-splashscreen', + templateUrl : './splashScreen.template.html', + styleUrls : [ + `./splashScreen.style.css`, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) + +export class SapiViewsCoreAtlasSplashScreen { + + public finishedLoading: boolean = false + + public atlases$ = this.sapiSvc.atlases$.pipe( + tap(() => this.finishedLoading = true) + ) + + constructor( + private store: Store<any>, + private sapiSvc: SAPI, + ) { + } + + public selectAtlas(atlas: SapiAtlasModel){ + this.store.dispatch( + atlasSelection.actions.selectAtlas({ + atlas + }) + ) + } +} diff --git a/src/atlasComponents/splashScreen/splashScreen/splashScreen.style.css b/src/atlasComponents/sapiViews/core/atlas/splashScreen/splashScreen.style.css similarity index 100% rename from src/atlasComponents/splashScreen/splashScreen/splashScreen.style.css rename to src/atlasComponents/sapiViews/core/atlas/splashScreen/splashScreen.style.css diff --git a/src/atlasComponents/splashScreen/splashScreen/splashScreen.template.html b/src/atlasComponents/sapiViews/core/atlas/splashScreen/splashScreen.template.html similarity index 87% rename from src/atlasComponents/splashScreen/splashScreen/splashScreen.template.html rename to src/atlasComponents/sapiViews/core/atlas/splashScreen/splashScreen.template.html index 6236bd010201f3b334dc28c58df491acdd3017d2..ab4954689175ea3d66d2333d659c952cb1a8604c 100644 --- a/src/atlasComponents/splashScreen/splashScreen/splashScreen.template.html +++ b/src/atlasComponents/sapiViews/core/atlas/splashScreen/splashScreen.template.html @@ -7,7 +7,7 @@ <mat-card (click)="selectAtlas(atlas)" matRipple - *ngFor="let atlas of loadedAtlases$ | async | filterArray : filterNullFn" + *ngFor="let atlas of atlases$ | async" class="m-3 col-md-12 col-lg-12 pe-all"> <mat-card-header> <mat-card-title class="text-nowrap font-stretch"> @@ -22,7 +22,7 @@ </mat-card> <ng-template [ngIf]="!finishedLoading"> - <div class="d-flex align-items-center p-4"> + <div class="d-flex align-items-center sxplr-p-4"> <h1 class="mat-h1"> <spinner-cmp></spinner-cmp> </h1> diff --git a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.component.ts b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..58a2151732937cd390d516ace5a2bfab2de87f7b --- /dev/null +++ b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.component.ts @@ -0,0 +1,217 @@ +import { animate, state, style, transition, trigger } from "@angular/animations"; +import { ChangeDetectionStrategy, Component, ElementRef, HostBinding, QueryList, ViewChild, ViewChildren } from "@angular/core"; +import { select, Store } from "@ngrx/store"; +import { combineLatest, forkJoin, merge, Observable, Subject, Subscription } from "rxjs"; +import { distinctUntilChanged, map, mapTo, shareReplay, switchMap, take } from "rxjs/operators"; +import { SAPI } from "src/atlasComponents/sapi"; +import { atlasSelection } from "src/state"; +import { fromRootStore } from "src/state/atlasSelection"; +import { IQuickTourData } from "src/ui/quickTour"; +import { ARIA_LABELS, CONST, QUICKTOUR_DESC } from 'common/constants' +import { MatMenuTrigger } from "@angular/material/menu"; +import { SapiParcellationModel, SapiSpaceModel } from "src/atlasComponents/sapi/type"; +import { InterSpaceCoordXformSvc } from "src/atlasComponents/sapi/core/space/interSpaceCoordXform.service" + +@Component({ + selector: `sxplr-sapiviews-core-atlas-tmplparcselector`, + templateUrl: './tmplParcSelector.template.html', + styleUrls: [ + `./tmplParcSelector.style.css` + ], + exportAs: 'sxplrSapiViewsCoreAtlasTmplparcselector', + animations: [ + trigger('toggleAtlasLayerSelector', [ + state('false', style({ + transform: 'scale(0)', + opacity: 0, + transformOrigin: '0% 100%' + })), + state('true', style({ + transform: 'scale(1)', + opacity: 1, + transformOrigin: '0% 100%' + })), + transition('false => true', [ + animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') + ]), + transition('true => false', [ + animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') + ]) + ]) + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) + +export class SapiViewsCoreAtlasAtlasTmplParcSelector { + + public ARIA_LABELS = ARIA_LABELS + public CONST = CONST + + @ViewChildren(MatMenuTrigger) + matMenuTriggers: QueryList<MatMenuTrigger> + + @ViewChild('selectorPanelTmpl', { read: ElementRef }) + selectorPanelTemplateRef: ElementRef + + private atp$ = this.store$.pipe( + fromRootStore.distinctATP() + ) + + public availableParcellations$ = this.store$.pipe( + fromRootStore.allAvailParcs(this.sapi), + shareReplay(1), + ) + + public availableTemplates$ = this.store$.pipe( + fromRootStore.allAvailSpaces(this.sapi), + ) + + public selectedTemplate$ = this.atp$.pipe( + map(({ template }) => template) + ) + + public selectedParcellation$ = this.atp$.pipe( + map(({ parcellation }) => parcellation) + ) + + public parcsAvailableInCurrentTmpl$: Observable<SapiParcellationModel[]> = combineLatest([ + this.atp$, + this.availableParcellations$, + ]).pipe( + switchMap(([{ atlas, template }, parcs]) => + forkJoin( + parcs.map( + parc => this.sapi.getParcellation(atlas["@id"], parc["@id"]).getVolumes().pipe( + map( + volumes => { + return { + parcellation: parc, + volumes + } + } + ) + ) + ) + ).pipe( + map(arr => + arr.filter( + item => item.volumes.find(vol => vol.data.space["@id"] === template["@id"]) + ).map( + ({ parcellation }) => parcellation + ) + ) + ) + ) + ) + + private showOverlayIntentByTemplate$ = new Subject() + private showOverlayIntentByParcellation$ = new Subject() + public showLoadingOverlay$ = merge( + this.showOverlayIntentByTemplate$.pipe( + mapTo(true) + ), + this.selectedTemplate$.pipe( + mapTo(false) + ), + this.showOverlayIntentByParcellation$.pipe( + mapTo(true) + ), + this.selectedParcellation$.pipe( + mapTo(false) + ) + ).pipe( + distinctUntilChanged(), + ) + + private subscriptions: Subscription[] = [] + + @HostBinding('attr.data-opened') + public selectorExpanded: boolean = false + + public quickTourData: IQuickTourData = { + order: 4, + description: QUICKTOUR_DESC.LAYER_SELECTOR, + } + + + constructor( + private store$: Store, + private sapi: SAPI, + private interSpaceCoordXformSvc: InterSpaceCoordXformSvc, + ) { + + } + ngOnDestroy() { + while (this.subscriptions.length) this.subscriptions.pop().unsubscribe() + } + + + toggleSelector() { + this.selectorExpanded = !this.selectorExpanded + /** + * on selector open, call transform end point + * this caches the result, and will not bottleneck when the user selects a new space + */ + if (this.selectorExpanded) { + forkJoin({ + availableTemplates: this.availableTemplates$.pipe( + take(1) + ), + selectedTemplate: this.selectedTemplate$.pipe( + take(1) + ), + navigation: this.store$.pipe( + select(atlasSelection.selectors.navigation), + take(1) + ) + }).pipe( + + ).subscribe(({ availableTemplates, selectedTemplate, navigation }) => { + for (const avail of availableTemplates) { + this.interSpaceCoordXformSvc.transform( + InterSpaceCoordXformSvc.TmplIdToValidSpaceName(selectedTemplate["@id"]), + InterSpaceCoordXformSvc.TmplIdToValidSpaceName(avail["@id"]), + navigation.position as [number, number, number] + ).subscribe() + } + }) + } + } + + closeSelector() { + this.selectorExpanded = false + } + + openSelector() { + this.selectorExpanded = true + } + + selectTemplate(tmpl: SapiSpaceModel) { + this.showOverlayIntentByTemplate$.next(true) + + this.store$.dispatch( + atlasSelection.actions.selectTemplate({ + template: tmpl + }) + ) + } + + selectParcellation(parc: SapiParcellationModel) { + this.showOverlayIntentByParcellation$.next(true) + + this.store$.dispatch( + atlasSelection.actions.selectParcellation({ + parcellation: parc + }) + ) + } + + collapseExpandedGroup() { + this.matMenuTriggers.forEach(t => t.menuOpen && t.closeMenu()) + } + + + trackTmpl(t:SapiSpaceModel) { + return t['@id'] + } +} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.stories.ts b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f18f737ec57e70b77c688c95f7fdbdc84c9e95b --- /dev/null +++ b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.stories.ts @@ -0,0 +1,138 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { provideMockStore } from "@ngrx/store/testing" +import { action } from "@storybook/addon-actions" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI } from "src/atlasComponents/sapi" +import { InterSpaceCoordXformSvc } from "src/atlasComponents/sapi/core/space/interSpaceCoordXform.service" +import { spaceId, provideDarkTheme, getHumanAtlas, getMni152, getJba29, getSpace, atlasId, getParc, parcId } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { atlasSelection } from "src/state" +import { SapiViewsCoreAtlasModule } from "../module" +import { SapiViewsCoreAtlasAtlasTmplParcSelector } from "./tmplParcSelector.component" + +const actionsData = { + selectTemplate: action('selectTemplate'), + selectParcellation: action('selectParcellation') +} + +export default { + component: SapiViewsCoreAtlasAtlasTmplParcSelector, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreAtlasModule, + AngularMaterialModule, + ], + providers: [ + SAPI, + InterSpaceCoordXformSvc, + ...provideDarkTheme, + ], + declarations: [ + ] + }) + ], +} as Meta + +const Template: Story<SapiViewsCoreAtlasAtlasTmplParcSelector> = (args: SapiViewsCoreAtlasAtlasTmplParcSelector, { loaded }) => { + const { + atlas, + template, + parcellation, + } = loaded + + return ({ + props: { + selectTemplate: actionsData.selectTemplate, + selectParcellation: actionsData.selectParcellation + }, + moduleMetadata: { + providers: [ + provideMockStore({ + initialState: { + [atlasSelection.nameSpace]: { + ...atlasSelection.defaultState, + selectedAtlas: atlas, + selectedTemplate: template, + selectedParcellation: parcellation, + } + } + }) + ] + } + }) +} +Template.loaders = [ + +] + +export const MNI152JBA29 = Template.bind({}) +MNI152JBA29.args = { + +} +MNI152JBA29.loaders = [ + async () => { + const atlas = await getHumanAtlas() + const template = await getMni152() + const parcellation = await getJba29() + return { + atlas, + template, + parcellation, + } + } +] + +export const BigBrainJBA29 = Template.bind({}) +BigBrainJBA29.args = { + +} +BigBrainJBA29.loaders = [ + async () => { + const atlas = await getHumanAtlas() + const template = await getSpace(atlasId.human, spaceId.human.bigbrain) + const parcellation = await getJba29() + return { + atlas, + template, + parcellation, + } + } +] + +export const BigBrainCorticalLayers = Template.bind({}) +BigBrainCorticalLayers.args = { + +} +BigBrainCorticalLayers.loaders = [ + async () => { + const atlas = await getHumanAtlas() + const template = await getSpace(atlasId.human, spaceId.human.bigbrain) + const parcellation = await getParc(atlasId.human, parcId.human.corticalLayers) + return { + atlas, + template, + parcellation, + } + } +] + +export const MNI152LongBundle = Template.bind({}) +MNI152LongBundle.args = { + +} +MNI152LongBundle.loaders = [ + async () => { + const atlas = await getHumanAtlas() + const template = await getMni152() + const parcellation = await getParc(atlasId.human, parcId.human.longBundle) + return { + atlas, + template, + parcellation, + } + } +] \ No newline at end of file diff --git a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.style.css b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.style.css similarity index 64% rename from src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.style.css rename to src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.style.css index 425a45a5e252f904956712930e5996740733d60f..49568298b447009881affb039d95cdd967cd675f 100644 --- a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.style.css +++ b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.style.css @@ -1,15 +1,9 @@ .selector-container { - overflow-y:scroll; - max-height: 80vh; - width: 21rem; - bottom: 4rem; - z-index: 5; -} - -:host-context([darktheme="true"]) .loading-overlay -{ - background-color: rgba(10, 10, 10, 0.8); + overflow-y:scroll; + max-height: 80vh; + width: 21rem; + bottom: 4rem; } .loading-overlay @@ -19,7 +13,7 @@ .loading-overlay { - position: absolute; + position: fixed; width: 100%; height: 100%; top: 0; @@ -38,3 +32,10 @@ grid-column: 2; grid-row: 2; } + +/* necessary to align the tiles to the start of grid tile */ +sxplr-sapiviews-core-space-tile, +sxplr-sapiviews-core-parcellation-tile +{ + height: 100%; +} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.template.html b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.template.html new file mode 100644 index 0000000000000000000000000000000000000000..db9ba08d8bf273f3726bda3fe2ae8d19b94f5b0a --- /dev/null +++ b/src/atlasComponents/sapiViews/core/atlas/tmplParcSelector/tmplParcSelector.template.html @@ -0,0 +1,103 @@ +<!-- selector panel when expanded --> + +<mat-card class="selector-container m-2 position-absolute" + [ngClass]="{'pe-all': selectorExpanded}" + [@toggleAtlasLayerSelector]="selectorExpanded" + (@toggleAtlasLayerSelector.done)="atlasSelectorTour?.attachTo(selectorExpanded ? selectorPanelTemplateRef : null)" + #selectorPanelTmpl> + + <mat-card-content> + + <!-- templates --> + <mat-card-subtitle> + {{ CONST.ATLAS_SELECTOR_LABEL_SPACES }} + </mat-card-subtitle> + + <!-- template grid and tiles --> + <mat-grid-list cols="3" + rowHeight="2:3" + gutterSize="16"> + + <mat-grid-tile *ngFor="let template of availableTemplates$ | async; trackBy: trackTmpl" + [attr.aria-checked]="(selectedTemplate$ | async)?.['@id'] === template['@id']"> + + <sxplr-sapiviews-core-space-tile + [ngClass]="{ + 'sxplr-extra-muted': !(template | spaceSupportedInCurrentParcellation | async) + }" + [sxplr-sapiviews-core-space-tile-space]="template" + [sxplr-sapiviews-core-space-tile-selected]="(selectedTemplate$ | async)?.['@id'] === template['@id']" + (click)="selectTemplate(template)"> + </sxplr-sapiviews-core-space-tile> + </mat-grid-tile> + </mat-grid-list> + + <mat-divider></mat-divider> + + <!-- parcellations --> + <mat-card-subtitle class="mt-2"> + {{ CONST.ATLAS_SELECTOR_LABEL_PARC_MAPS }} + </mat-card-subtitle> + + <mat-grid-list cols="3" + rowHeight="2:3" + gutterSize="16"> + + <!-- using single parc template, since it's reused in non individual parcellation and tmpl for grp parcellation --> + <ng-template #singleParcTmpl let-parc> + <sxplr-sapiviews-core-parcellation-tile + [ngClass]="{ + 'sxplr-extra-muted': !(parc | parcellationSupportedInCurrentSpace | async) + }" + [sxplr-sapiviews-core-parcellation-tile-selected]="(selectedParcellation$ | async)?.['@id'] === parc['@id']" + [sxplr-sapiviews-core-parcellation-tile-parcellation]="parc" + (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="selectParcellation($event)"> + + </sxplr-sapiviews-core-parcellation-tile> + </ng-template> + + <mat-grid-tile *ngFor="let parc of availableParcellations$ | async | filterUnsupportedParc | filterGroupedParcs"> + <ng-template + [ngTemplateOutlet]="singleParcTmpl" + [ngTemplateOutletContext]="{ $implicit: parc }"> + </ng-template> + </mat-grid-tile> + + <mat-grid-tile *ngFor="let group of availableParcellations$ | async | filterUnsupportedParc | filterGroupedParcs : true | filterUnsupportedParc"> + <sxplr-sapiviews-core-parcellation-tile + [sxplr-sapiviews-core-parcellation-tile-groupmenu-parc-tmpl]="singleParcTmpl" + [sxplr-sapiviews-core-parcellation-tile-parcellation]="group" + (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="selectParcellation($event)"> + + </sxplr-sapiviews-core-parcellation-tile> + </mat-grid-tile> + </mat-grid-list> + + </mat-card-content> + + <div [ngClass]="{ + 'sxplr-d-none': !(showLoadingOverlay$ | async) + }" + class="loading-overlay"> + <spinner-cmp class="spinner"></spinner-cmp> + </div> + + +</mat-card> + +<!-- place holder when not expanded --> +<div class="position-relative m-2 cursor-pointer scale-up-bl pe-all sxplr-d-inline-block" + quick-tour + [quick-tour-description]="quickTourData.description" + [quick-tour-order]="quickTourData.order" + #atlasSelectorTour="quickTour"> + <!-- TODO check when do we disable atlas selector --> + <button color="primary" + *ngIf="true" + matTooltip="Select layer" + mat-mini-fab + [attr.aria-label]="ARIA_LABELS.TOGGLE_ATLAS_LAYER_SELECTOR" + (click)="toggleSelector()"> + <i class="fas fa-layer-group"></i> + </button> +</div> diff --git a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..88614d6194880e18d3e802de65c1660f75557b13 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts @@ -0,0 +1,28 @@ +import { Component, Input, OnChanges, SimpleChanges } from "@angular/core"; +import { SapiDatasetModel } from "src/atlasComponents/sapi"; +import { CONST } from "common/constants" + +const RESTRICTED_ACCESS_ID = "https://nexus.humanbrainproject.org/v0/data/minds/core/embargostatus/v1.0.0/3054f80d-96a8-4dce-9b92-55c68a8b5efd" + +@Component({ + selector: `sxplr-sapiviews-core-datasets-dataset`, + templateUrl: './dataset.template.html', + styleUrls: [ + `./dataset.style.css` + ] +}) + +export class DatasetView implements OnChanges{ + @Input('sxplr-sapiviews-core-datasets-dataset-input') + dataset: SapiDatasetModel + + public isRestricted = false + public CONST = CONST + + ngOnChanges(changes: SimpleChanges): void { + const { dataset } = changes + if (dataset) { + this.isRestricted = (dataset.currentValue as SapiDatasetModel)?.metadata?.accessibility?.["@id"] === RESTRICTED_ACCESS_ID + } + } +} diff --git a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.stories.ts b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..acec7a744768e383bdb511d15b4a8d537b9e0633 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.stories.ts @@ -0,0 +1,57 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI } from "src/atlasComponents/sapi" +import { getHoc1RightFeatureDetail, getHoc1RightFeatures, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { SapiViewsCoreDatasetModule } from ".." +import { DatasetView } from "./dataset.component" + +export default { + component: DatasetView, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreDatasetModule, + ], + providers: [ + SAPI, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<DatasetView> = (args: DatasetView, { loaded }) => { + const { feature } = loaded + return ({ + props: { + ...args, + dataset: feature, + }, + }) +} + +const loadFeat = async () => { + const features = await getHoc1RightFeatures() + const receptorfeat = features.find(f => f['@type'] === "siibra/core/dataset") + const feature = await getHoc1RightFeatureDetail(receptorfeat["@id"]) + return { + feature + } +} + +export const ReceptorDataset = Template.bind({}) +ReceptorDataset.args = { + +} +ReceptorDataset.loaders = [ + async () => { + const { feature } = await loadFeat() + return { + feature + } + } +] \ No newline at end of file diff --git a/src/atlasComponents/regionalFeatures/bsFeatures/receptor/profile/profile.style.css b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.style.css similarity index 100% rename from src/atlasComponents/regionalFeatures/bsFeatures/receptor/profile/profile.style.css rename to src/atlasComponents/sapiViews/core/datasets/dataset/dataset.style.css diff --git a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html new file mode 100644 index 0000000000000000000000000000000000000000..f9c37f9907d70750b2de1db334ac2c15949db7ca --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html @@ -0,0 +1,47 @@ +<ng-template #headerTmpl> + <ng-content select="[header]"></ng-content> +</ng-template> + +<mat-card *ngIf="!dataset"> + + <ng-template [ngTemplateOutlet]="headerTmpl"></ng-template> + <span> + Dataset not specified. + </span> +</mat-card> + +<mat-card *ngIf="dataset" + class="mat-elevation-z4 sxplr-z-4"> + <mat-card-title> + <ng-template [ngTemplateOutlet]="headerTmpl"></ng-template> + <span> + {{ dataset.metadata.fullName }} + </span> + </mat-card-title> + + <mat-card-subtitle class="sxplr-d-inline-flex sxplr-align-items-stretch"> + <mat-icon class="sxplr-m-a" fontSet="fas" fontIcon="fa-database"></mat-icon> + <span class="sxplr-m-a"> + EBRAINS dataset + </span> + + <button *ngIf="isRestricted" + [matTooltip]="CONST.GDPR_TEXT" + mat-icon-button color="warn"> + <i class="fas fa-exclamation-triangle"></i> + </button> + + <mat-divider class="sxplr-pl-1" [vertical]="true"></mat-divider> + + <a mat-icon-button *ngFor="let url of dataset.urls" [href]="url.doi | parseDoi" target="_blank"> + <i class="fas fa-external-link-alt"></i> + </a> + </mat-card-subtitle> +</mat-card> + +<mat-card *ngIf="dataset" class="sxplr-z-0"> + <mat-card-content> + <markdown-dom class="sxplr-muted" [markdown]="dataset?.metadata?.description"> + </markdown-dom> + </mat-card-content> +</mat-card> diff --git a/src/atlasComponents/sapiViews/core/datasets/index.ts b/src/atlasComponents/sapiViews/core/datasets/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..d265981874bb6564d2dac8f9738d7069436154e9 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/index.ts @@ -0,0 +1,7 @@ +export { + SapiViewsCoreDatasetModule +} from "./module" + +export { + DatasetView +} from "./dataset/dataset.component" \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/datasets/module.ts b/src/atlasComponents/sapiViews/core/datasets/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..b295da6bc25f1a9fdc98031a42810ba907013320 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/module.ts @@ -0,0 +1,23 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { MarkdownModule } from "src/components/markdown"; +import { AngularMaterialModule } from "src/sharedModules"; +import { SapiViewsUtilModule } from "../../util/module"; +import { DatasetView } from "./dataset/dataset.component"; + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule, + MarkdownModule, + SapiViewsUtilModule + ], + declarations: [ + DatasetView, + ], + exports: [ + DatasetView + ] +}) + +export class SapiViewsCoreDatasetModule{} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/index.ts b/src/atlasComponents/sapiViews/core/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb3d0ffce18ea93d534e44866392f0fc819b0ae8 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/index.ts @@ -0,0 +1,7 @@ +export { + SapiViewsCoreModule +} from "./module" + +export { + SapiViewsCoreSpaceBoundingBox +} from "./space" \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/module.ts b/src/atlasComponents/sapiViews/core/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..c30a1d83ebe1a72a911acbfdc796729e683431d8 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/module.ts @@ -0,0 +1,30 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SapiViewsCoreAtlasModule } from "./atlas/module"; +import { SapiViewsCoreDatasetModule } from "./datasets"; +import { SapiViewsCoreParcellationModule } from "./parcellation/module"; +import { SapiViewsCoreRegionModule } from "./region"; +import { SapiViewsCoreRichModule } from "./rich/module"; +import { SapiViewsCoreSpaceModule } from "./space"; + +@NgModule({ + imports: [ + CommonModule, + SapiViewsCoreDatasetModule, + SapiViewsCoreRegionModule, + SapiViewsCoreAtlasModule, + SapiViewsCoreSpaceModule, + SapiViewsCoreParcellationModule, + SapiViewsCoreRichModule, + ], + exports: [ + SapiViewsCoreDatasetModule, + SapiViewsCoreRegionModule, + SapiViewsCoreAtlasModule, + SapiViewsCoreSpaceModule, + SapiViewsCoreParcellationModule, + SapiViewsCoreRichModule, + ] +}) + +export class SapiViewsCoreModule{} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.component.ts b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..13358ff46b1a3eadc2b2e5ddb9fe417675ad94e3 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.component.ts @@ -0,0 +1,26 @@ +import { Component, Input, Output, EventEmitter } from "@angular/core"; +import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; + +@Component({ + selector: `sxplr-sapiviews-core-parcellation-chip`, + templateUrl: './parcellation.chip.template.html', + styleUrls: [ + `./parcellation.chip.style.css` + ], +}) + +export class SapiViewsCoreParcellationParcellationChip { + + @Input('sxplr-sapiviews-core-parcellation-chip-parcellation') + parcellation: SapiParcellationModel + + @Input('sxplr-sapiviews-core-parcellation-chip-color') + color: 'default' | 'primary' | 'accent' | 'warn' = "default" + + @Output('sxplr-sapiviews-core-parcellation-chip-onclick') + onClick = new EventEmitter<MouseEvent>() + + click(event: MouseEvent) { + this.onClick.emit(event) + } +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.stories.ts b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..5e3afe419fc640d609b2f6acbfee703a7e5ae69c --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.stories.ts @@ -0,0 +1,120 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { provideMockStore } from "@ngrx/store/testing" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI, SapiParcellationModel } from "src/atlasComponents/sapi" +import { atlasId, getAtlas, provideDarkTheme, getParc } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { SapiViewsCoreParcellationModule } from "../module" +import { SapiViewsCoreParcellationParcellationChip } from "./parcellation.chip.component" + + +export default { + component: SapiViewsCoreParcellationParcellationChip, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreParcellationModule, + AngularMaterialModule, + ], + providers: [ + provideMockStore(), + SAPI, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<SapiViewsCoreParcellationParcellationChip> = (args: SapiViewsCoreParcellationParcellationChip, { loaded, parameters }) => { + const { + parcellation + } = loaded + const { + contentProjection + } = parameters + + return ({ + props: { + ...args, + parcellation + }, + template: ` + <sxplr-sapiviews-core-parcellation-chip> + ${contentProjection || ''} + </sxplr-sapiviews-core-parcellation-chip> + ` + }) +} +Template.loaders = [] + +const asyncLoader = async (_atlasId: string) => { + const parcs: SapiParcellationModel[] = [] + const atlasDetail = await getAtlas(_atlasId) + + for (const parc of atlasDetail.parcellations) { + const parcDetail = await getParc(atlasDetail['@id'], parc['@id']) + parcs.push(parcDetail) + } + return { + parcs + } +} + +const getContentProjection = ({ prefix = null, suffix = null }) => { + let returnVal = `` + if (prefix) { + returnVal += `<div prefix>${prefix}</div>` + } + if (suffix) { + returnVal += `<div suffix>${suffix}</div>` + } + return returnVal +} + +export const Default = Template.bind({}) +Default.loaders = [ + async () => { + const { + parcs + } = await asyncLoader(atlasId.human) + return { + parcellation: parcs[0] + } + } +] + +export const Prefix = Template.bind({}) +Prefix.loaders = [ + ...Default.loaders +] +Prefix.parameters = { + contentProjection: getContentProjection({ + prefix: `PREFIX`, + }) +} + +export const Suffix = Template.bind({}) +Suffix.loaders = [ + ...Default.loaders +] +Suffix.parameters = { + contentProjection: getContentProjection({ + suffix: `SUFFIX`, + }) +} + + +export const PrefixSuffix = Template.bind({}) +PrefixSuffix.loaders = [ + ...Default.loaders +] +PrefixSuffix.parameters = { + contentProjection: getContentProjection({ + prefix: `PREFIX`, + suffix: `SUFFIX`, + }) +} diff --git a/src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.style.css b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.style.css similarity index 100% rename from src/atlasComponents/regionalFeatures/singleFeatures/iEEGRecordings/iEEGRecordings/iEEGRecordings.style.css rename to src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.style.css diff --git a/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.template.html b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.template.html new file mode 100644 index 0000000000000000000000000000000000000000..417575bf67914c3886697a435134eb17852b1676 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/chip/parcellation.chip.template.html @@ -0,0 +1,16 @@ +<mat-chip-list *ngIf="parcellation"> + <mat-chip [selected]="color !== 'default'" + (click)="click($event)" + [color]="color"> + + <ng-content select="[prefix]"> + </ng-content> + + <span class="mat-body sxplr-white-space-nowrap"> + {{ parcellation.name }} + </span> + + <ng-content select="[suffix]"> + </ng-content> + </mat-chip> +</mat-chip-list> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/filterGroupedParcellations.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/filterGroupedParcellations.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..62910eab45e5529ee22f137afa92e4978d42cfe4 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/filterGroupedParcellations.pipe.ts @@ -0,0 +1,31 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { SapiParcellationModel } from "src/atlasComponents/sapi"; +import { GroupedParcellation } from "./groupedParcellation"; + +@Pipe({ + name: `filterGroupedParcs`, + pure: true +}) + +export class FilterGroupedParcellationPipe implements PipeTransform{ + public transform(parcs: SapiParcellationModel[], getGroupsFlag: boolean=false): (SapiParcellationModel|GroupedParcellation)[] { + if (!getGroupsFlag) { + return parcs.filter(p => !p.modality) + } + const map: Record<string, SapiParcellationModel[]> = {} + for (const parc of parcs.filter(p => !!p.modality)) { + if (!map[parc.modality]) { + map[parc.modality] = [] + } + map[parc.modality].push(parc) + } + + const returnGrps: GroupedParcellation[] = [] + for (const key in map) { + returnGrps.push( + new GroupedParcellation(key, map[key]) + ) + } + return returnGrps + } +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/filterUnsupportedParc.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/filterUnsupportedParc.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a9d1a30ec5a9bc9ed281cca3e44051cd7b5b163 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/filterUnsupportedParc.pipe.ts @@ -0,0 +1,36 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; +import { GroupedParcellation } from "./groupedParcellation"; + +type Filterables = SapiParcellationModel | GroupedParcellation + +const unsupportedIds = [ + "https://doi.org/10.1016/j.jneumeth.2020.108983/mni152", + "https://identifiers.org/neurovault.image:23262" +] + +const hideGroup = [ + "cytoarchitecture" +] + +@Pipe({ + name: `filterUnsupportedParc`, + pure: true +}) + +export class FilterUnsupportedParcPipe implements PipeTransform{ + public transform<T extends Filterables>(parcs: T[]): T[] { + return (parcs || []).filter(p => { + if (p instanceof GroupedParcellation) { + return hideGroup.indexOf(p.name) < 0 + } + if (unsupportedIds.includes(p["@id"])) { + return false + } + if (p.version) { + return !p.version.deprecated + } + return true + }) + } +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/groupedParcellation.ts b/src/atlasComponents/sapiViews/core/parcellation/groupedParcellation.ts new file mode 100644 index 0000000000000000000000000000000000000000..c66d6e9818a3884cb0c48fc58de56bee4373e113 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/groupedParcellation.ts @@ -0,0 +1,10 @@ +import { SapiParcellationModel } from "src/atlasComponents/sapi" + +export class GroupedParcellation{ + name: string + parcellations: SapiParcellationModel[] + constructor(name: string, parcellations: SapiParcellationModel[]){ + this.name = name + this.parcellations = parcellations + } +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/index.ts b/src/atlasComponents/sapiViews/core/parcellation/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..801b1f3e381a6a806c94cd6458e3187a620755c1 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/index.ts @@ -0,0 +1,11 @@ +export { + SapiViewsCoreParcellationModule +} from "./module" + +export { + FilterGroupedParcellationPipe +} from "./filterGroupedParcellations.pipe" + +export { + FilterUnsupportedParcPipe +} from "./filterUnsupportedParc.pipe" \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/module.ts b/src/atlasComponents/sapiViews/core/parcellation/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb7581d2d88ab9f43518261e87a515b8ceb99456 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/module.ts @@ -0,0 +1,62 @@ +import { CommonModule } from "@angular/common"; +import { APP_INITIALIZER, NgModule } from "@angular/core"; +import { Store } from "@ngrx/store"; +import { ComponentsModule } from "src/components"; +import { AngularMaterialModule } from "src/sharedModules"; +import { atlasAppearance } from "src/state"; +import { UtilModule } from "src/util"; +import { SapiViewsUtilModule } from "../../util"; +import { SapiViewsCoreParcellationParcellationChip } from "./chip/parcellation.chip.component"; +import { FilterGroupedParcellationPipe } from "./filterGroupedParcellations.pipe"; +import { FilterUnsupportedParcPipe } from "./filterUnsupportedParc.pipe"; +import { ParcellationIsBaseLayer } from "./parcellationIsBaseLayer.pipe"; +import { ParcellationVisibilityService } from "./parcellationVis.service"; +import { PreviewParcellationUrlPipe } from "./previewParcellationUrl.pipe"; +import { SapiViewsCoreParcellationParcellationSmartChip } from "./smartChip/parcellation.smartChip.component"; +import { SapiViewsCoreParcellationParcellationTile } from "./tile/parcellation.tile.component"; + +@NgModule({ + imports: [ + CommonModule, + ComponentsModule, + AngularMaterialModule, + UtilModule, + SapiViewsUtilModule, + ], + declarations: [ + SapiViewsCoreParcellationParcellationTile, + SapiViewsCoreParcellationParcellationChip, + SapiViewsCoreParcellationParcellationSmartChip, + PreviewParcellationUrlPipe, + FilterGroupedParcellationPipe, + FilterUnsupportedParcPipe, + ParcellationIsBaseLayer, + ], + exports: [ + SapiViewsCoreParcellationParcellationTile, + SapiViewsCoreParcellationParcellationChip, + SapiViewsCoreParcellationParcellationSmartChip, + FilterGroupedParcellationPipe, + FilterUnsupportedParcPipe, + ], + providers: [ + ParcellationVisibilityService, + { + provide: APP_INITIALIZER, + useFactory: (store: Store, svc: ParcellationVisibilityService) => { + svc.visibility$.subscribe(val => { + store.dispatch( + atlasAppearance.actions.setShowDelineation({ + flag: val + }) + ) + }) + return () => Promise.resolve() + }, + multi: true, + deps: [ Store, ParcellationVisibilityService ] + } + ] +}) + +export class SapiViewsCoreParcellationModule{} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c3c09a9d9b7307a7a1f8b18c193565137e9bd2f --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe.ts @@ -0,0 +1,44 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; + +const baseLayerIds = [ + /** + * julich brain + */ + "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290", + "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-25", + "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579", + + /** + * allen mouse + */ + "minds/core/parcellationatlas/v1.0.0/05655b58-3b6f-49db-b285-64b5a0276f83", + "minds/core/parcellationatlas/v1.0.0/39a1384b-8413-4d27-af8d-22432225401f", + + /** + * waxholm + */ + "minds/core/parcellationatlas/v1.0.0/11017b35-7056-4593-baad-3934d211daba", + "minds/core/parcellationatlas/v1.0.0/2449a7f0-6dd0-4b5a-8f1e-aec0db03679d", + "minds/core/parcellationatlas/v1.0.0/ebb923ba-b4d5-4b82-8088-fa9215c2e1fe", + "minds/core/parcellationatlas/v1.0.0/ebb923ba-b4d5-4b82-8088-fa9215c2e1fe-v4", + + /** + * monkey + */ + "minds/core/parcellationatlas/v1.0.0/mebrains-tmp-id", +] + +@Pipe({ + name: 'parcellationIsBaseLayer', + pure: true +}) + +export class ParcellationIsBaseLayer implements PipeTransform{ + public transform(parc: SapiParcellationModel): boolean { + /** + * currently, the only base layer is cyto maps + */ + return baseLayerIds.includes(parc["@id"]) + } +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..5a7eb1fc32405cd1a4f9128cfda54c018eb45766 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts @@ -0,0 +1,52 @@ +import { IDS } from "src/atlasComponents/sapi/constants" +import { SAPI } from "src/atlasComponents/sapi/sapi.service" +import { SapiParcellationModel } from "src/atlasComponents/sapi/type" +import { getTraverseFunctions } from "./parcellationVersion.pipe" + +describe("parcellationVersion.pipe.ts", () => { + describe("getTraverseFunctions", () => { + let julichBrainParcellations: SapiParcellationModel[] = [] + beforeAll(async () => { + const res = await fetch(`${SAPI.bsEndpoint}/atlases/${encodeURIComponent(IDS.ATLAES.HUMAN)}/parcellations`) + const arr: SapiParcellationModel[] = await res.json() + julichBrainParcellations = arr.filter(it => /Julich-Brain Cytoarchitectonic Maps/.test(it.name)) + }) + it("> should be at least 3 parcellations", () => { + expect(julichBrainParcellations.length).toBeGreaterThanOrEqual(3) + }) + + const scenarios = [{ + name: "default", + inputFlag: undefined, + expect25: false + },{ + name: "skipDeprecated set to true", + inputFlag: true, + expect25: false + },{ + name: "skipDeprecated set to false", + inputFlag: false, + expect25: true + }] + + for (const { name, inputFlag, expect25} of scenarios) { + describe(name, () => { + it(`expect to find 25: ${expect25}`, () => { + const { findNewer, findOldest } = typeof inputFlag === "undefined" + ? getTraverseFunctions(julichBrainParcellations) + : getTraverseFunctions(julichBrainParcellations, inputFlag) + let cursor: SapiParcellationModel = findOldest() + let foundFlag: boolean = false + while (cursor) { + if (cursor.name === "Julich-Brain Cytoarchitectonic Maps 2.5") { + if (expect25) foundFlag = true + break + } + cursor = findNewer(cursor) + } + expect(foundFlag).toEqual(expect25) + }) + }) + } + }) +}) \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..a2e5e1f3ff73e2b3ec837188c52c5670e4348e46 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.ts @@ -0,0 +1,92 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; + +export function getTraverseFunctions(parcellations: SapiParcellationModel[], skipDeprecated: boolean = true) { + + const getTraverse = (key: 'prev' | 'next') => { + + const returnFunction = (parc: SapiParcellationModel) => { + if (!parc.version) { + throw new Error(`parcellation ${parc.name} does not have version defined!`) + } + if (!parc.version[key]) { + return null + } + const found = parcellations.find(p => p["@id"] === parc.version[key]["@id"]) + if (!found) { + throw new Error(`parcellation ${parc.name} references ${parc.version[key]['@id']} as ${key} version, but it cannot be found.`) + } + if (skipDeprecated && found.version.deprecated) { + return returnFunction(found) + } + return found + } + + return returnFunction + } + + const findNewer = getTraverse('next') + const findOlder = getTraverse('prev') + + const getFindMostFn = (findNewest) => { + const useFn = findNewest + ? findNewer + : findOlder + return () => { + let cursor = parcellations[0] + let returnParc: SapiParcellationModel + while (cursor) { + returnParc = cursor + cursor = useFn(cursor) + } + return returnParc + } + } + + return { + findNewer, + findOlder, + findNewest: getFindMostFn(true), + findOldest: getFindMostFn(false) + } + +} + + +@Pipe({ + name: 'orderParcellationByVersion', + pure: true +}) + +export class OrderParcellationByVersionPipe implements PipeTransform{ + public transform(parcellations: SapiParcellationModel[], newestFirst: boolean = true, index: number = 0) { + const { + findNewer, + findOlder + } = getTraverseFunctions(parcellations) + + const findMostFn = newestFirst ? findNewer : findOlder + const tranverseFn = newestFirst ? findOlder : findNewer + + const mostParc = (() => { + let cursor = parcellations[0] + let returnParc: SapiParcellationModel + while (cursor) { + returnParc = cursor + cursor = findMostFn(cursor) + } + return returnParc + })() + + let idx = 0 + let cursor = mostParc + while (idx < index) { + cursor = tranverseFn(cursor) + if (!cursor) { + throw new Error(`index out of bound`) + } + idx ++ + } + return cursor + } +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationVis.service.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationVis.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..3eb329a9b6be3b9ec42afdb82986f460f01b9d15 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationVis.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +@Injectable({ + providedIn: 'root' +}) + +export class ParcellationVisibilityService { + private _visibility$ = new BehaviorSubject<boolean>(true) + public readonly visibility$ = this._visibility$.asObservable() + + setVisibility(flag: boolean) { + this._visibility$.next(flag) + } + + toggleVisibility(){ + this.setVisibility( + !this._visibility$.getValue() + ) + } +} diff --git a/src/atlasComponents/parcellation/getParcPreviewUrl.pipe.ts b/src/atlasComponents/sapiViews/core/parcellation/previewParcellationUrl.pipe.ts similarity index 69% rename from src/atlasComponents/parcellation/getParcPreviewUrl.pipe.ts rename to src/atlasComponents/sapiViews/core/parcellation/previewParcellationUrl.pipe.ts index 3f70d60e90bdb1645d44f74edbe86ce7b6aec463..7c1a543002a3f533271b778e209c9b39f241b0d8 100644 --- a/src/atlasComponents/parcellation/getParcPreviewUrl.pipe.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/previewParcellationUrl.pipe.ts @@ -1,12 +1,14 @@ -import { Pipe, PipeTransform } from "@angular/core" +import { Pipe, PipeTransform } from "@angular/core"; +import { SapiParcellationModel } from "src/atlasComponents/sapi"; +import { GroupedParcellation } from "./groupedParcellation"; const previewImgMap = new Map([ ['minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579', 'cytoarchitectonic-maps.png'], ['juelich/iav/atlas/v1.0.0/3', 'cortical-layers.png'], ['juelich/iav/atlas/v1.0.0/4', 'grey-white-matter.png'], - ['juelich/iav/atlas/v1.0.0/5', 'firbe-long.png'], - ['juelich/iav/atlas/v1.0.0/6', 'firbe-short.png'], + ['juelich/iav/atlas/v1.0.0/5', 'fibre-long.png'], + ['juelich/iav/atlas/v1.0.0/6', 'fibre-short.png'], ['minds/core/parcellationatlas/v1.0.0/d80fbab2-ce7f-4901-a3a2-3c8ef8a3b721', 'difumo-64.png'], ['minds/core/parcellationatlas/v1.0.0/73f41e04-b7ee-4301-a828-4b298ad05ab8', 'difumo-128.png'], ['minds/core/parcellationatlas/v1.0.0/141d510f-0342-4f94-ace7-c97d5f160235', 'difumo-256.png'], @@ -30,20 +32,22 @@ const previewImgMap = new Map([ * used for directories */ const previewNameToPngMap = new Map([ - ['fibre architecture', 'firbe-long.png'], + ['fibre architecture', 'fibre-long.png'], ['functional modes', 'difumo-128.png'] ]) @Pipe({ - name: 'getParcPreviewUrl', + name: 'previewParcellationUrl', pure: true }) -export class GetParcPreviewUrlPipe implements PipeTransform{ - public transform(tile: any){ - const filename = tile['@id'] - ? previewImgMap.get(tile['@id']) - : previewNameToPngMap.get(tile['name']) +export class PreviewParcellationUrlPipe implements PipeTransform{ + public transform(tile: SapiParcellationModel | GroupedParcellation): string { + if (tile instanceof GroupedParcellation) { + const filename = previewNameToPngMap.get(tile.name) + return filename && `assets/images/atlas-selection/${filename}` + } + const filename = previewImgMap.get(tile['@id']) return filename && `assets/images/atlas-selection/${filename}` } } diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..27d4a53cc941a95164407b86f61825c01df1527f --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.component.ts @@ -0,0 +1,104 @@ +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChange, SimpleChanges } from "@angular/core"; +import { BehaviorSubject, concat, Observable, of, timer } from "rxjs"; +import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; +import { ParcellationVisibilityService } from "../parcellationVis.service"; +import { ARIA_LABELS } from "common/constants" +import { getTraverseFunctions } from "../parcellationVersion.pipe"; +import { mapTo, shareReplay, switchMap } from "rxjs/operators"; + +@Component({ + selector: `sxplr-sapiviews-core-parcellation-smartchip`, + templateUrl: `./parcellation.smartChip.template.html`, + styleUrls: [ + `./parcellation.smartChip.style.css` + ] +}) + +export class SapiViewsCoreParcellationParcellationSmartChip implements OnChanges{ + + public ARIA_LABELS = ARIA_LABELS + + @Input('sxplr-sapiviews-core-parcellation-smartchip-parcellation') + parcellation: SapiParcellationModel + + @Input('sxplr-sapiviews-core-parcellation-smartchip-all-parcellations') + parcellations: SapiParcellationModel[] + + @Output('sxplr-sapiviews-core-parcellation-smartchip-dismiss-nonbase-layer') + onDismiss = new EventEmitter<SapiParcellationModel>() + + @Output('sxplr-sapiviews-core-parcellation-smartchip-select-parcellation') + onSelectParcellation = new EventEmitter<SapiParcellationModel>() + + constructor( + private svc: ParcellationVisibilityService + ){ + + } + + otherVersions: SapiParcellationModel[] + + ngOnChanges(changes: SimpleChanges) { + const { parcellation } = changes + if (parcellation) { + this.onDismissClicked$.next(false) + } + this.otherVersions = [] + if (!this.parcellation) { + return + } + this.otherVersions = [ this.parcellation ] + if (!this.parcellations || this.parcellations.length === 0) { + return + } + if (!this.parcellation.version) { + return + } + + this.otherVersions = [] + + const { + findNewest, + findOlder + } = getTraverseFunctions(this.parcellations) + + let cursor: SapiParcellationModel = findNewest() + while (cursor) { + this.otherVersions.push(cursor) + cursor = findOlder(cursor) + } + } + + loadingParc$: Observable<SapiParcellationModel> = this.onSelectParcellation.pipe( + switchMap(parc => concat( + of(parc), + timer(5000).pipe( + mapTo(null) + ), + )), + shareReplay(1), + ) + + parcellationVisibility$: Observable<boolean> = this.svc.visibility$ + + toggleParcellationVisibility(){ + this.svc.toggleVisibility() + } + + dismiss(){ + if (this.onDismissClicked$.value) return + this.onDismissClicked$.next(true) + this.onDismiss.emit(this.parcellation) + } + + selectParcellation(parc: SapiParcellationModel){ + if (this.trackByFn(parc) === this.trackByFn(this.parcellation)) return + this.onSelectParcellation.emit(parc) + } + + trackByFn(parc: SapiParcellationModel){ + return parc["@id"] + } + + onDismissClicked$ = new BehaviorSubject<boolean>(false) +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.stories.ts b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..054c0c70eacd03943a5db1465bd412f4a8353c04 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.stories.ts @@ -0,0 +1,143 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Component, Input } from "@angular/core" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { action } from "@storybook/addon-actions" +import { SAPI, SapiParcellationModel } from "src/atlasComponents/sapi" +import { atlasId, getAtlas, provideDarkTheme, getParc, getAtlases } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { SapiViewsCoreParcellationModule } from "../module" +import { provideMockStore } from "@ngrx/store/testing" +import { within, userEvent } from '@storybook/testing-library'; +import { ARIA_LABELS } from "common/constants" +import { ParcellationVisibilityService } from "../parcellationVis.service" +import { of } from "rxjs" + +@Component({ + selector: `parc-smart-chip-wrapper`, + template: ` + <mat-accordion> + <mat-expansion-panel *ngFor="let item of parcRecords | keyvalue"> + <mat-expansion-panel-header> + {{ item.key }} + </mat-expansion-panel-header> + + <div class="sxplr-of-x-scroll sxplr-white-space-nowrap"> + <sxplr-sapiviews-core-parcellation-smartchip *ngFor="let parc of item.value | filterUnsupportedParc" + [sxplr-sapiviews-core-parcellation-smartchip-parcellation]="parc" + [sxplr-sapiviews-core-parcellation-smartchip-all-parcellations]="item.value" + (sxplr-sapiviews-core-parcellation-smartchip-select-parcellation)="selectParcellation($event)"> + </sxplr-sapiviews-core-parcellation-smartchip> + </div> + + </mat-expansion-panel> + </mat-accordion> + `, + styles: [ + `sxplr-sapiviews-core-parcellation-chip { display: block; }` + ] +}) + +class ParcSmartChipWrapper{ + @Input() + parcRecords: Record<string, SapiParcellationModel[]> = {} + + selectParcellation(parc: SapiParcellationModel){ + + } +} + + +export default { + component: ParcSmartChipWrapper, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreParcellationModule, + AngularMaterialModule, + ], + providers: [ + provideMockStore(), + SAPI, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<ParcSmartChipWrapper> = (args: ParcSmartChipWrapper, { loaded, parameters }) => { + const { + parcRecords + } = loaded + const { providers = [] } = parameters + + return ({ + props: { + ...args, + selectParcellation: action("selectParcellation"), + parcRecords + }, + moduleMetadata: { + providers + } + }) +} +Template.loaders = [] + +const asyncLoader = async () => { + const parcRecords: Record<string, SapiParcellationModel[]> = {} + + for (const species in atlasId) { + + const atlasDetail = await getAtlas(atlasId[species]) + parcRecords[species] = [] + for (const parc of atlasDetail.parcellations) { + const parcDetail = await getParc(atlasDetail['@id'], parc['@id']) + parcRecords[species].push(parcDetail) + } + } + + return { + parcRecords + } +} + +export const Default = Template.bind({}) +Default.loaders = [ + async () => { + const { + parcRecords + } = await asyncLoader() + return { + parcRecords + } + } +] + +export const TestInteraction = Template.bind({}) +TestInteraction.loaders = [ + ...Default.loaders +] +TestInteraction.parameters = { + providers: [ + { + provide: ParcellationVisibilityService, + useValue: { + setVisibility: action('setVisibility'), + toggleVisibility: action('toggleVisibility'), + visibility$: of(true) + } + } + ] +} +TestInteraction.play = async ({ args, canvasElement }) => { + const canvas = within(canvasElement) + + await userEvent.click(canvas.getByText("human")) + const allEye = canvas.getAllByText(ARIA_LABELS.TOGGLE_DELINEATION) + await userEvent.hover(allEye[0]) + await userEvent.click(allEye[0]) +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.style.css b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.style.css new file mode 100644 index 0000000000000000000000000000000000000000..e8d0ca357ccbfe577e65a2f9d78c1589227cbf33 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.style.css @@ -0,0 +1,23 @@ +.otherversion-wrapper +{ + position: relative; + overflow: hidden; + margin: 0.5rem; +} + +.otherversion-wrapper.loading > .spinner-container +{ + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + + display: flex; + align-items: center; +} + +.otherversion-wrapper.loading > .spinner-container > spinner-cmp +{ + margin: 0.5rem; +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html new file mode 100644 index 0000000000000000000000000000000000000000..e56b6b69b9fc9405c4efb29900f9a448921839cf --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/smartChip/parcellation.smartChip.template.html @@ -0,0 +1,78 @@ +<mat-menu #otherParcMenu="matMenu" + [hasBackdrop]="false" + class="parc-smart-chip-menu-panel sxplr-bg-none sxplr-of-x-hidden sxplr-box-shadow-none sxplr-mxw-80vw"> + <div (iav-outsideClick)="menuTrigger.closeMenu()"> + + <div *ngFor="let parc of otherVersions" + class="otherversion-wrapper" + [ngClass]="{ + 'loading': (loadingParc$ | async) === parc + }"> + + + <sxplr-sapiviews-core-parcellation-chip + [ngClass]="{ + 'sxplr-blink': (loadingParc$ | async) === parc + }" + [sxplr-sapiviews-core-parcellation-chip-parcellation]="parc" + [sxplr-sapiviews-core-parcellation-chip-color]="(parcellation | equality : parc : trackByFn) ? 'primary' : 'default'" + (sxplr-sapiviews-core-parcellation-chip-onclick)="selectParcellation(parc)"> + + </sxplr-sapiviews-core-parcellation-chip> + + <div class="spinner-container" *ngIf="(loadingParc$ | async) === parc"> + <spinner-cmp> + </spinner-cmp> + </div> + </div> + </div> + +</mat-menu> + +<sxplr-sapiviews-core-parcellation-chip + [ngClass]="{ + 'sxplr-muted': !(parcellationVisibility$ | async), + 'sxplr-blink': onDismissClicked$ | async + }" + class="sxplr-d-inline-block" + [sxplr-sapiviews-core-parcellation-chip-parcellation]="parcellation" + [sxplr-sapiviews-core-parcellation-chip-color]="(parcellation | parcellationIsBaseLayer) ? 'default' : 'primary'" + (sxplr-sapiviews-core-parcellation-chip-onclick)="menuTrigger.toggleMenu()" + [matMenuTriggerFor]="otherParcMenu" + #menuTrigger="matMenuTrigger" + > + + <div prefix class="sxplr-scale-70"> + <button mat-mini-fab + [color]="(parcellationVisibility$ | async) ? 'primary' : 'default'" + [matTooltip]="ARIA_LABELS.TOGGLE_DELINEATION" + iav-stop="mousedown click" + [iav-key-listener]="[{'type': 'keydown', 'key': 'q', 'capture': true, 'target': 'document' }]" + (iav-key-event)="toggleParcellationVisibility()" + (click)="toggleParcellationVisibility()"> + <i class="fas" + [ngClass]="(parcellationVisibility$ | async) ? 'fa-eye': 'fa-eye-slash'" + aria-hidden="true"> + </i> + <span class="sr-only"> + {{ ARIA_LABELS.TOGGLE_DELINEATION }} + </span> + </button> + </div> + + <div *ngIf="!(parcellation | parcellationIsBaseLayer)" + class="sxplr-scale-70" + suffix> + <button mat-mini-fab + color="primary" + iav-stop="mousedown click" + (click)="dismiss()"> + + <spinner-cmp class="sxplr-w-100 sxplr-h-100" *ngIf="onDismissClicked$ | async; else defaultDismissIcon"></spinner-cmp> + <ng-template #defaultDismissIcon> + <i class="fas fa-times"></i> + </ng-template> + + </button> + </div> +</sxplr-sapiviews-core-parcellation-chip> diff --git a/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.component.ts b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..839e8791ff2df8090de142deedf0e39046cfec32 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.component.ts @@ -0,0 +1,57 @@ +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef } from "@angular/core"; +import { SapiParcellationModel } from "src/atlasComponents/sapi"; +import { GroupedParcellation } from "../groupedParcellation"; + +const lightthemeId = [ + 'juelich/iav/atlas/v1.0.0/3', + 'juelich/iav/atlas/v1.0.0/4', +] + +@Component({ + selector: `sxplr-sapiviews-core-parcellation-tile`, + templateUrl: './parcellation.tile.template.html', + styleUrls: [ + `./parcellation.tile.style.css` + ], +}) + +export class SapiViewsCoreParcellationParcellationTile implements OnChanges{ + @Input('sxplr-sapiviews-core-parcellation-tile-groupmenu-parc-tmpl') + singleParcTmpl: TemplateRef<any> + + private _parcellation: SapiParcellationModel | GroupedParcellation + @Input('sxplr-sapiviews-core-parcellation-tile-parcellation') + set parcellation(val: SapiParcellationModel | GroupedParcellation) { + this._parcellation = val + this.ngOnChanges() + } + get parcellation(){ + return this._parcellation + } + + @Input('sxplr-sapiviews-core-parcellation-tile-selected') + selected: boolean = false + + @Output('sxplr-sapiviews-core-parcellation-tile-onclick-parc') + onClickOnParcellation = new EventEmitter<SapiParcellationModel>() + + public gutterSize = "2" + public rowHeight = "6:11" + + public darktheme = false + public pureParc: SapiParcellationModel + public dirParc: GroupedParcellation + + ngOnChanges(): void { + if (this.parcellation instanceof GroupedParcellation) { + this.dirParc = this.parcellation + } else { + this.pureParc = this.parcellation + } + this.darktheme = !!this.dirParc || lightthemeId.indexOf(this.parcellation['@id']) < 0 + } + + clickOnParcellation(parc: SapiParcellationModel){ + this.onClickOnParcellation.emit(parc) + } +} diff --git a/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.stories.ts b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..1fad5b1cd9332700829f662798829e919691b956 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.stories.ts @@ -0,0 +1,160 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Component, Input, Output, EventEmitter } from "@angular/core" +import { provideMockStore } from "@ngrx/store/testing" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI, SapiParcellationModel } from "src/atlasComponents/sapi" +import { atlasId, parcId, getAtlas, provideDarkTheme, getParc } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { SapiViewsCoreParcellationModule } from "../module" + +@Component({ + selector: `parc-tile-wrapper`, + template: ` + <ng-template #grpParcTmpl let-parc> + {{ parc.name }} + </ng-template> + + <mat-accordion> + <mat-expansion-panel *ngFor="let item of parcs | keyvalue"> + + <mat-expansion-panel-header> + {{ item.key }} + </mat-expansion-panel-header> + + <ng-template matExpansionPanelContent> + <div class="sxplr-d-inline-flex align-items-start"> + <sxplr-sapiviews-core-parcellation-tile + *ngFor="let parc of item.value" + [sxplr-sapiviews-core-parcellation-tile-parcellation]="parc" + [sxplr-sapiviews-core-parcellation-tile-selected]="parc['@id'] === selected" + class="sxplr-m-2" + (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="parcClicked.emit($event)"> + </sxplr-sapiviews-core-parcellation-tile> + </div> + </ng-template> + + </mat-expansion-panel> + + <mat-expansion-panel> + <mat-expansion-panel-header> + > grouped + </mat-expansion-panel-header> + + <ng-template matExpansionPanelContent> + <ng-container *ngFor="let item of parcs | keyvalue"> + <sxplr-sapiviews-core-parcellation-tile + *ngFor="let parc of (item.value | filterGroupedParcs : true)" + [sxplr-sapiviews-core-parcellation-tile-parcellation]="parc" + class="sxplr-m-2" + (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="parcClicked.emit($event)"> + </sxplr-sapiviews-core-parcellation-tile> + </ng-container> + </ng-template> + </mat-expansion-panel> + + <mat-expansion-panel> + <mat-expansion-panel-header> + > grouped tmpl + </mat-expansion-panel-header> + + <ng-template matExpansionPanelContent> + <ng-container *ngFor="let item of parcs | keyvalue"> + <sxplr-sapiviews-core-parcellation-tile + *ngFor="let parc of (item.value | filterGroupedParcs : true)" + [sxplr-sapiviews-core-parcellation-tile-groupmenu-parc-tmpl]="grpParcTmpl" + [sxplr-sapiviews-core-parcellation-tile-parcellation]="parc" + class="sxplr-m-2" + (sxplr-sapiviews-core-parcellation-tile-onclick-parc)="parcClicked.emit($event)"> + </sxplr-sapiviews-core-parcellation-tile> + </ng-container> + </ng-template> + </mat-expansion-panel> + </mat-accordion> + `, + styles: [ + `sxplr-sapiviews-core-parcellation-tile { display: inline-block; max-width: 8rem; }` + ] +}) + +class ParcTileWrapper{ + @Input() + parcs: Record<string, SapiParcellationModel[]> = {} + + @Input() + selected: string = parcId.human.longBundle + + @Output() + parcClicked = new EventEmitter() +} + +export default { + component: ParcTileWrapper, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreParcellationModule, + AngularMaterialModule, + ], + providers: [ + provideMockStore(), + SAPI, + ...provideDarkTheme, + ], + declarations: [ + ParcTileWrapper + ] + }) + ], +} as Meta + +const Template: Story<ParcTileWrapper> = (args: ParcTileWrapper, { loaded }) => { + const { + parcs + } = loaded + + return ({ + props: { + ...args, + parcs + } + }) +} +Template.loaders = [ + +] + +const asyncLoader = async () => { + const parcs: Record<string, SapiParcellationModel[]> = {} + for (const species in atlasId) { + const atlasDetail = await getAtlas(atlasId[species]) + parcs[species] = [] + + for (const parc of atlasDetail.parcellations) { + const parcDetail = await getParc(atlasDetail['@id'], parc['@id']) + parcs[species].push(parcDetail) + } + } + + return { + parcs + } +} + +export const Default = Template.bind({}) +Default.args = { + selected: parcId.human.longBundle +} +Default.loaders = [ + + async () => { + const { + parcs + } = await asyncLoader() + return { + parcs + } + } +] diff --git a/src/atlasComponents/regionalFeatures/util.ts b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.style.css similarity index 100% rename from src/atlasComponents/regionalFeatures/util.ts rename to src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.style.css diff --git a/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.template.html b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.template.html new file mode 100644 index 0000000000000000000000000000000000000000..19cd424794e7bc343d775fa541979bf1019cf0ac --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/tile/parcellation.tile.template.html @@ -0,0 +1,69 @@ +<mat-menu hasBackDrop="false" #matMenu="matMenu"> + + <ng-template matMenuContent let-subParcellations="subParcellations"> + + <div class="sxplr-custom-cmp sxplr-ml-2 sxplr-mr-2"> + <mat-grid-list + cols="1" + [rowHeight]="rowHeight" + [gutterSize]="gutterSize"> + + <mat-grid-tile + *ngFor="let parc of subParcellations"> + + + <!-- if parent component injected template, use injected template --> + <ng-template + [ngIf]="singleParcTmpl" + [ngIfElse]="fallbackGrpParcTmpl" + [ngTemplateOutlet]="singleParcTmpl" + [ngTemplateOutletContext]="{ + $implicit: parc + }"> + </ng-template> + + <ng-template #fallbackGrpParcTmpl> + <tile-cmp *ngIf="parc" + class="sxplr-custom-cmp text" + [tile-text]="parc.name" + [tile-image-src]="parc | previewParcellationUrl" + [tile-selected]="selected" + [tile-image-darktheme]="darktheme" + (click)="clickOnParcellation(parc)"> + + </tile-cmp> + </ng-template> + + </mat-grid-tile> + </mat-grid-list> + </div> + </ng-template> +</mat-menu> + +<ng-template [ngIf]="parcellation"> + + <tile-cmp *ngIf="pureParc" + [tile-text]="pureParc.name" + [tile-image-src]="pureParc | previewParcellationUrl" + [tile-selected]="selected" + [tile-image-darktheme]="darktheme" + (click)="clickOnParcellation(pureParc)" + > + + </tile-cmp> + + + <tile-cmp *ngIf="dirParc" + [tile-text]="dirParc.name" + [tile-image-src]="dirParc | previewParcellationUrl" + [tile-selected]="selected" + [tile-image-darktheme]="darktheme" + tile-is-dir="true" + [matMenuTriggerFor]="matMenu" + [matMenuTriggerData]="{ + subParcellations: dirParc.parcellations || [] + }" + > + </tile-cmp> + +</ng-template> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/parcellation/tile/singleTile.stories.ts b/src/atlasComponents/sapiViews/core/parcellation/tile/singleTile.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..920c1cfc82a26550a2710a584f6db9f45b0ea5af --- /dev/null +++ b/src/atlasComponents/sapiViews/core/parcellation/tile/singleTile.stories.ts @@ -0,0 +1,90 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { provideMockStore } from "@ngrx/store/testing" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI, SapiParcellationModel } from "src/atlasComponents/sapi" +import { parcId, provideDarkTheme, getParc, getHumanAtlas } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { FilterGroupedParcellationPipe } from "../filterGroupedParcellations.pipe" +import { GroupedParcellation } from "../groupedParcellation" +import { SapiViewsCoreParcellationModule } from "../module" +import { SapiViewsCoreParcellationParcellationTile } from "./parcellation.tile.component" + +export default { + component: SapiViewsCoreParcellationParcellationTile, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreParcellationModule, + AngularMaterialModule, + ], + providers: [ + provideMockStore(), + SAPI, + ...provideDarkTheme, + ], + declarations: [ + + ], + }), + ], +} as Meta + +const Template: Story<SapiViewsCoreParcellationParcellationTile> = (args: SapiViewsCoreParcellationParcellationTile, { loaded }) => { + const { + groups + } = loaded + + const { + gutterSize, + rowHeight + } = args + return ({ + props: { + gutterSize, + rowHeight, + parcellation: groups[1] + }, + styles: [ + `sxplr-sapiviews-core-parcellation-tile { display: inline-block; max-width: 8rem; }` + ] + }) +} +Template.loaders = [ + +] + +const asyncLoader = async () => { + const parcs: SapiParcellationModel[] = [] + + const atlasDetail = await getHumanAtlas() + + for (const parc of atlasDetail.parcellations) { + const parcDetail = await getParc(atlasDetail['@id'], parc['@id']) + parcs.push(parcDetail) + } + + const pipe = new FilterGroupedParcellationPipe() + const groups = pipe.transform(parcs, true) as GroupedParcellation[] + return { + groups + } +} + +export const Default = Template.bind({}) +Default.args = { + selected: parcId.human.longBundle +} +Default.loaders = [ + + async () => { + const { + groups + } = await asyncLoader() + return { + groups + } + } +] diff --git a/src/atlasComponents/sapiViews/core/region/index.ts b/src/atlasComponents/sapiViews/core/region/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..8b76cf0a9a6de3c8d738f9fb145fb97a7faceb61 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/index.ts @@ -0,0 +1 @@ +export { SapiViewsCoreRegionModule } from "./module" \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/region/module.ts b/src/atlasComponents/sapiViews/core/region/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c30611aa3b4ce8494f21bc60501da87089b9bb6 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/module.ts @@ -0,0 +1,39 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { MarkdownModule } from "src/components/markdown"; +import { SpinnerModule } from "src/components/spinner"; +import { AngularMaterialModule } from "src/sharedModules"; +import { SapiViewsFeaturesModule } from "../../features"; +import { SapiViewsUtilModule } from "../../util/module"; +import { SapiViewsCoreRegionRegionChip } from "./region/chip/region.chip.component"; +import { SapiViewsCoreRegionRegionListItem } from "./region/listItem/region.listItem.component"; +import { SapiViewsCoreRegionRegionBase } from "./region/region.base.directive"; +import { SapiViewsCoreRegionRegionalFeatureDirective } from "./region/region.features.directive"; +import { SapiViewsCoreRegionRegionRich } from "./region/rich/region.rich.component"; + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule, + SapiViewsUtilModule, + SapiViewsFeaturesModule, + SpinnerModule, + MarkdownModule, + ], + declarations: [ + SapiViewsCoreRegionRegionListItem, + SapiViewsCoreRegionRegionRich, + SapiViewsCoreRegionRegionChip, + SapiViewsCoreRegionRegionBase, + SapiViewsCoreRegionRegionalFeatureDirective, + ], + exports: [ + SapiViewsCoreRegionRegionListItem, + SapiViewsCoreRegionRegionRich, + SapiViewsCoreRegionRegionChip, + SapiViewsCoreRegionRegionBase, + SapiViewsCoreRegionRegionalFeatureDirective, + ] +}) + +export class SapiViewsCoreRegionModule{} diff --git a/src/atlasComponents/sapiViews/core/region/region/chip/region.chip.component.ts b/src/atlasComponents/sapiViews/core/region/region/chip/region.chip.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..1852f3e602a60d3739dc5bffa89a190b64fb33f7 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/chip/region.chip.component.ts @@ -0,0 +1,20 @@ +import { Component, EventEmitter, Output } from "@angular/core"; +import { SapiViewsCoreRegionRegionBase } from "../region.base.directive"; + +@Component({ + selector: `sxplr-sapiviews-core-region-region-chip`, + templateUrl: `./region.chip.template.html`, + styleUrls: [ + `./region.chip.style.css` + ] +}) + +export class SapiViewsCoreRegionRegionChip extends SapiViewsCoreRegionRegionBase { + shouldFetchDetail = true + @Output('sxplr-sapiviews-core-region-region-chip-clicked') + clickEmitter = new EventEmitter<MouseEvent>() + + onClick(event: MouseEvent){ + this.clickEmitter.emit(event) + } +} diff --git a/src/atlasComponents/sapiViews/core/region/region/chip/region.chip.stories.ts b/src/atlasComponents/sapiViews/core/region/region/chip/region.chip.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..ec74e25aadf0b5f3d00ef164efd2ff19238259bb --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/chip/region.chip.stories.ts @@ -0,0 +1,134 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI } from "src/atlasComponents/sapi" +import { provideDarkTheme, getHumanAtlas, getJba29, getMni152, getHoc1Right, get44Left } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { SapiViewsCoreRegionModule } from "../../module" +import { SapiViewsCoreRegionRegionChip } from "./region.chip.component" + + +export default { + component: SapiViewsCoreRegionRegionChip, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreRegionModule, + AngularMaterialModule, + ], + providers: [ + SAPI, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<SapiViewsCoreRegionRegionChip> = (args: SapiViewsCoreRegionRegionChip, { loaded, parameters }) => { + const { + atlas, + parcellation, + template, + region, + } = loaded + const { + contentProjection + } = parameters + + return ({ + props: { + atlas, + parcellation, + template, + region, + }, + template: ` + <sxplr-sapiviews-core-region-region-chip> + ${contentProjection || ''} + </sxplr-sapiviews-core-region-region-chip> + ` + }) +} +Template.loaders = [] + +const getContentProjection = ({ prefix = null, suffix = null }) => { + let returnVal = `` + if (prefix) { + returnVal += `<div prefix>${prefix}</div>` + } + if (suffix) { + returnVal += `<div suffix>${suffix}</div>` + } + return returnVal +} + +export const Default = Template.bind({}) +Default.loaders = [ + async () => { + + const atlas = await getHumanAtlas() + const parcellation = await getJba29() + const template = await getMni152() + const region = await getHoc1Right() + + return { + atlas, + parcellation, + template, + region, + } + } +] + +export const Dark = Template.bind({}) +Dark.loaders = [ + async () => { + + const atlas = await getHumanAtlas() + const parcellation = await getJba29() + const template = await getMni152() + const region = await get44Left() + + return { + atlas, + parcellation, + template, + region, + } + } +] + +export const Prefix = Template.bind({}) +Prefix.loaders = [ + ...Default.loaders +] +Prefix.parameters = { + contentProjection: getContentProjection({ + prefix: `PREFIX`, + }) +} + +export const Suffix = Template.bind({}) +Suffix.loaders = [ + ...Default.loaders +] +Suffix.parameters = { + contentProjection: getContentProjection({ + suffix: `SUFFIX`, + }) +} + + +export const PrefixSuffix = Template.bind({}) +PrefixSuffix.loaders = [ + ...Default.loaders +] +PrefixSuffix.parameters = { + contentProjection: getContentProjection({ + prefix: `PREFIX`, + suffix: `SUFFIX`, + }) +} diff --git a/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.style.css b/src/atlasComponents/sapiViews/core/region/region/chip/region.chip.style.css similarity index 100% rename from src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.style.css rename to src/atlasComponents/sapiViews/core/region/region/chip/region.chip.style.css diff --git a/src/atlasComponents/sapiViews/core/region/region/chip/region.chip.template.html b/src/atlasComponents/sapiViews/core/region/region/chip/region.chip.template.html new file mode 100644 index 0000000000000000000000000000000000000000..9b8262ee0441e16ce49b34d4a6d43ea9b60833b8 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/chip/region.chip.template.html @@ -0,0 +1,35 @@ +<ng-template #prefixTmpl> + <ng-content select="[prefix]"></ng-content> +</ng-template> + +<ng-template #suffixTmpl> + <ng-content select="[suffix]"></ng-content> +</ng-template> + +<div *ngIf="!region"> + <div class="sxplr-d-inline-block"> + <ng-template [ngTemplateOutlet]="prefixTmpl"></ng-template> + </div> + <spinner-cmp class="sxplr-d-inline-block"></spinner-cmp> + <div class="sxplr-d-inline-block"> + <ng-template [ngTemplateOutlet]="suffixTmpl"></ng-template> + </div> +</div> + +<mat-chip-list *ngIf="region" + [ngClass]="{ + 'darktheme': regionDarkmode, + 'lighttheme': !regionDarkmode + }"> + <mat-chip + (click)="onClick($event)" + class="sxplr-custom-cmp text" + [style.backgroundColor]="regionRgbString" + > + <ng-template [ngTemplateOutlet]="prefixTmpl"></ng-template> + <span class="mat-body"> + {{ region.name }} + </span> + <ng-template [ngTemplateOutlet]="suffixTmpl"></ng-template> + </mat-chip> +</mat-chip-list> diff --git a/src/atlasComponents/sapiViews/core/region/region/listItem/region.listItem.component.ts b/src/atlasComponents/sapiViews/core/region/region/listItem/region.listItem.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..449b7fa3f4947b07df2c2df7afac1d7e04e4e738 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/listItem/region.listItem.component.ts @@ -0,0 +1,15 @@ +import { Component, Input } from "@angular/core"; +import { SapiViewsCoreRegionRegionBase } from "../region.base.directive"; + +@Component({ + selector: 'sxplr-sapiviews-core-region-region-list-item', + templateUrl: './region.listItem.template.html', + styleUrls: [ + `./region.listItem.style.css` + ] +}) + +export class SapiViewsCoreRegionRegionListItem extends SapiViewsCoreRegionRegionBase { + @Input('sxplr-sapiviews-core-region-region-list-item-ripple') + ripple: boolean = false +} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/region/region/listItem/region.listItem.stories.ts b/src/atlasComponents/sapiViews/core/region/region/listItem/region.listItem.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..168477a6197b9418f405375288e83b374a728680 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/listItem/region.listItem.stories.ts @@ -0,0 +1,122 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI } from "src/atlasComponents/sapi" +import { getHoc1Right, getHumanAtlas, getJba29, getMni152, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { SapiViewsCoreRegionModule } from "../../module" +import { SapiViewsCoreRegionRegionListItem } from "./region.listItem.component" + +export default { + component: SapiViewsCoreRegionRegionListItem, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreRegionModule, + ], + providers: [ + SAPI, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<SapiViewsCoreRegionRegionListItem> = (args: SapiViewsCoreRegionRegionListItem, { loaded, parameters }) => { + const { + human, + mni152, + jba29, + hoc1left + } = loaded + + const { contentProjection } = parameters + return ({ + props: { + atlas: human, + template: mni152, + parcellation: jba29, + region: hoc1left, + ripple: true + }, + template: ` + <sxplr-sapiviews-core-region-region-list-item> + ${contentProjection || ''} + </sxplr-sapiviews-core-region-region-list-item> + ` + }) +} + +const loadRegions = async () => { + + const human = await getHumanAtlas() + const mni152 = await getMni152() + const jba29 = await getJba29() + const hoc1left = await getHoc1Right(mni152["@id"]) + + return { + human, + mni152, + jba29, + hoc1left, + } +} + +export const HumanMni152Jba29Hoc1Left = Template.bind({}) +HumanMni152Jba29Hoc1Left.args = { + +} +HumanMni152Jba29Hoc1Left.loaders = [ + async () => { + const { + human, + mni152, + jba29, + hoc1left, + } = await loadRegions() + return { + human, + mni152, + jba29, + hoc1left, + } + } +] + + +const getPrefixSuffix = (prefix: string, suffix: string) => { + let returnVal = `` + if (prefix) { + returnVal += `<div prefix>${prefix}</div>` + } + if (suffix) { + returnVal += `<div suffix>${suffix}</div>` + } + return returnVal +} + +export const HeaderContentProjectionPrefix = Template.bind({}) +HeaderContentProjectionPrefix.loaders = [ + ...HumanMni152Jba29Hoc1Left.loaders +] +HeaderContentProjectionPrefix.parameters = { + contentProjection: getPrefixSuffix(`<i class="far fa-square"></i>`, null) +} + +export const HeaderContentProjectionSuffix = Template.bind({}) +HeaderContentProjectionSuffix.loaders = [ + ...HumanMni152Jba29Hoc1Left.loaders +] +HeaderContentProjectionSuffix.parameters = { + contentProjection: getPrefixSuffix(null, `<i class="fab fa-chrome"></i>`) +} + +export const HeaderContentProjectionBox = Template.bind({}) +HeaderContentProjectionBox.loaders = [ + ...HumanMni152Jba29Hoc1Left.loaders +] +HeaderContentProjectionBox.parameters = { + contentProjection: getPrefixSuffix(`TEXT`, `<button mat-raised-button>test button</button>`) +} diff --git a/src/atlasViewerExports/sampleBox/sampleBox.style.css b/src/atlasComponents/sapiViews/core/region/region/listItem/region.listItem.style.css similarity index 100% rename from src/atlasViewerExports/sampleBox/sampleBox.style.css rename to src/atlasComponents/sapiViews/core/region/region/listItem/region.listItem.style.css diff --git a/src/atlasComponents/sapiViews/core/region/region/listItem/region.listItem.template.html b/src/atlasComponents/sapiViews/core/region/region/listItem/region.listItem.template.html new file mode 100644 index 0000000000000000000000000000000000000000..4c833e9218409ff1ed338d4cc781dc852742fd52 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/listItem/region.listItem.template.html @@ -0,0 +1,7 @@ +<div *ngIf="region" matRipple [matRippleDisabled]="!ripple" class="sxplr-d-inline-flex"> + <ng-content select="[prefix]"></ng-content> + <span class="mat-body"> + {{ region.name }} + </span> + <ng-content select="[suffix]"></ng-content> +</div> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/region/region/region.base.directive.ts b/src/atlasComponents/sapiViews/core/region/region/region.base.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..96f374b267ced1fb700333654b7e62a39a9e311a --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/region.base.directive.ts @@ -0,0 +1,109 @@ +import { Directive, EventEmitter, Input, OnDestroy, Output } from "@angular/core"; +import { SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; +import { rgbToHsl } from 'common/util' +import { SAPI } from "src/atlasComponents/sapi/sapi.service"; +import { BehaviorSubject, Subject } from "rxjs"; +import { SAPIRegion } from "src/atlasComponents/sapi/core"; + +@Directive({ + selector: `[sxplr-sapiviews-core-region]`, + exportAs: "sapiViewsCoreRegion" +}) +export class SapiViewsCoreRegionRegionBase { + + @Input('sxplr-sapiviews-core-region-detail-flag') + shouldFetchDetail = false + + public fetchInProgress$ = new BehaviorSubject<boolean>(false) + + @Input('sxplr-sapiviews-core-region-atlas') + atlas: SapiAtlasModel + @Input('sxplr-sapiviews-core-region-template') + template: SapiSpaceModel + @Input('sxplr-sapiviews-core-region-parcellation') + parcellation: SapiParcellationModel + + @Output('sxplr-sapiviews-core-region-navigate-to') + onNavigateTo = new EventEmitter<number[]>() + + protected region$ = new Subject<SapiRegionModel>() + private _region: SapiRegionModel + @Input('sxplr-sapiviews-core-region-region') + set region(val: SapiRegionModel) { + + this.region$.next(val) + + if (!this.shouldFetchDetail || !val) { + this._region = val + this.setupRegionDarkmode() + return + } + this.fetchInProgress$.next(true) + this._region = null + + this.fetchDetail(val) + .then(r => { + this._region = r + }) + .catch(e => { + console.warn(`populating detail failed.`, e) + this._region = val + }) + .finally(() => { + this.fetchInProgress$.next(false) + this.setupRegionDarkmode() + }) + } + get region(){ + return this._region + } + + regionRgbString: string = `rgb(200, 200, 200)` + regionDarkmode = false + // in mm!! + regionPosition: number[] = null + dois: string[] = [] + + protected setupRegionDarkmode(){ + + this.regionRgbString = `rgb(200, 200, 200)` + this.regionDarkmode = false + this.regionPosition = null + this.dois = [] + + if (this.region) { + + /** + * color + */ + const rgb = SAPIRegion.GetDisplayColor(this.region) + this.regionRgbString = `rgb(${rgb.join(',')})` + const [_h, _s, l] = rgbToHsl(...rgb) + this.regionDarkmode = l < 0.4 + + /** + * position + */ + this.regionPosition = this.region.hasAnnotation?.bestViewPoint?.coordinates.map(v => v.value) + + /** + * dois + */ + this.dois = (this.region.hasAnnotation?.inspiredBy || []) + .map(insp => insp["@id"] as string) + .filter(id => /^https?:\/\/doi\.org/.test(id)) + } + } + + navigateTo(position: number[]) { + this.onNavigateTo.emit(position.map(v => v*1e6)) + } + + protected async fetchDetail(region: SapiRegionModel) { + return this.sapi.getRegion(this.atlas["@id"],this.parcellation["@id"], region.name).getDetail(this.template["@id"]).toPromise() + } + + constructor(protected sapi: SAPI){ + + } +} diff --git a/src/atlasComponents/sapiViews/core/region/region/region.features.directive.ts b/src/atlasComponents/sapiViews/core/region/region/region.features.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..47a4b6912c7a701b116fae6e13c0591c4005ab19 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/region.features.directive.ts @@ -0,0 +1,51 @@ +import { Directive, OnChanges, SimpleChanges } from "@angular/core"; +import { BehaviorSubject, Observable } from "rxjs"; +import { switchMap, filter, startWith, shareReplay, finalize } from "rxjs/operators"; +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionalFeatureModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; +import { SxplrCleanedFeatureModel } from "src/atlasComponents/sapi/type"; +import { SapiViewsCoreRegionRegionBase } from "./region.base.directive"; + +@Directive({ + selector: '[sxplr-sapiviews-core-region-regional-feature]', + exportAs: 'sapiViewsRegionalFeature' +}) + +export class SapiViewsCoreRegionRegionalFeatureDirective extends SapiViewsCoreRegionRegionBase implements OnChanges{ + + private ATPR$ = new BehaviorSubject<{ + atlas: SapiAtlasModel + template: SapiSpaceModel + parcellation: SapiParcellationModel + region: SapiRegionModel + }>(null) + + ngOnChanges(sc: SimpleChanges): void { + const { atlas, template, parcellation, region } = this + this.ATPR$.next({ atlas, template, parcellation, region }) + } + + constructor(sapi: SAPI){ + super(sapi) + } + + private features$: Observable<(SapiRegionalFeatureModel|SxplrCleanedFeatureModel)[]> = this.ATPR$.pipe( + filter(arg => { + if (!arg) return false + const { atlas, parcellation, region, template } = arg + return !!atlas && !!parcellation && !!region && !!template + }), + switchMap(({ atlas, parcellation, region, template }) => { + this.busy$.next(true) + return this.sapi.getRegionFeatures(atlas["@id"], parcellation["@id"], template["@id"], region.name).pipe( + finalize(() => this.busy$.next(false)) + ) + }), + ) + + public listOfFeatures$: Observable<(SapiRegionalFeatureModel|SxplrCleanedFeatureModel)[]> = this.features$.pipe( + startWith([]), + shareReplay(1), + ) + + public busy$ = new BehaviorSubject<boolean>(false) +} diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..79e4d693d5b23cd7a380f26c6cc8e5b922bba450 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts @@ -0,0 +1,52 @@ +import { Observable, Subject } from "rxjs"; +import { Component, EventEmitter, Inject, Output } from "@angular/core"; +import { DARKTHEME } from "src/util/injectionTokens"; +import { SapiViewsCoreRegionRegionBase } from "../region.base.directive"; +import { ARIA_LABELS, CONST } from 'common/constants' +import { SapiRegionalFeatureModel } from "src/atlasComponents/sapi"; +import { SAPI } from "src/atlasComponents/sapi/sapi.service"; + +@Component({ + selector: 'sxplr-sapiviews-core-region-region-rich', + templateUrl: './region.rich.template.html', + styleUrls: [ + `./region.rich.style.css` + ], + exportAs: "sapiViewsCoreRegionRich" +}) + +export class SapiViewsCoreRegionRegionRich extends SapiViewsCoreRegionRegionBase { + + shouldFetchDetail = true + public ARIA_LABELS = ARIA_LABELS + public CONST = CONST + + @Output('sxplr-sapiviews-core-region-region-rich-feature-clicked') + featureClicked = new EventEmitter<SapiRegionalFeatureModel>() + + public expandedPanel: string + + constructor( + sapi: SAPI, + @Inject(DARKTHEME) public darktheme$: Observable<boolean>, + ){ + super(sapi) + } + + handleRegionalFeatureClicked(feat: SapiRegionalFeatureModel) { + this.featureClicked.emit(feat) + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + handleExpansionPanelClosedEv(title: string){ + this.expandedPanel = null + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + handleExpansionPanelAfterExpandEv(title: string) { + this.expandedPanel = title + } + + activePanelTitles$: Observable<string[]> = new Subject() + +} diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.stories.ts b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..662a98325822bcfe069a3f8a4465dfb251e1af56 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.stories.ts @@ -0,0 +1,121 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI } from "src/atlasComponents/sapi" +import { getHoc1Right, getHumanAtlas, getJba29, getJba29Regions, getMni152, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { SapiViewsCoreRegionModule } from "../../module" +import { SapiViewsCoreRegionRegionRich } from "./region.rich.component" +import { action } from '@storybook/addon-actions'; +import {CUSTOM_ELEMENTS_SCHEMA} from "@angular/core"; +import {AngularMaterialModule} from "src/sharedModules"; +import {provideMockStore} from "@ngrx/store/testing"; + +const actionsData = { + onNavigateTo: action('onNavigateTo'), + onHandleRegionalFeatureClicked: action('onHandleRegionalFeatureClicked') +} + +export default { + component: SapiViewsCoreRegionRegionRich, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreRegionModule, + AngularMaterialModule, + ], + providers: [ + SAPI, + provideMockStore(), + ...provideDarkTheme, + ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<SapiViewsCoreRegionRegionRich> = (args: SapiViewsCoreRegionRegionRich, { loaded, parameters }) => { + const { + human, + mni152, + jba29, + hoc1left + } = loaded + + const { contentProjection } = parameters + return ({ + props: { + atlas: human, + template: mni152, + parcellation: jba29, + region: hoc1left, + navigateTo: actionsData.onNavigateTo, + handleRegionalFeatureClicked: actionsData.onHandleRegionalFeatureClicked + }, + template: ` + <sxplr-sapiviews-core-region-region-rich> + ${contentProjection || ''} + </sxplr-sapiviews-core-region-region-rich> + ` + }) +} + +const loadRegions = async () => { + + const human = await getHumanAtlas() + const mni152 = await getMni152() + const jba29 = await getJba29() + const hoc1left = await getHoc1Right(mni152["@id"]) + return { + human, + mni152, + jba29, + hoc1left, + } +} + +export const HumanMni152Jba29Hoc1Left = Template.bind({}) +HumanMni152Jba29Hoc1Left.args = { + +} +HumanMni152Jba29Hoc1Left.loaders = [ + async () => { + const { + human, + mni152, + jba29, + hoc1left, + } = await loadRegions() + return { + human, + mni152, + jba29, + hoc1left, + } + } +] + + +export const HeaderContentProjection = Template.bind({}) +HeaderContentProjection.loaders = [ + ...HumanMni152Jba29Hoc1Left.loaders +] +HeaderContentProjection.parameters = { + contentProjection: `<div header>HEADER CONTENT PROJECTED</div>` +} + +export const InjectionSimpleRegion = Template.bind({}) +InjectionSimpleRegion.loaders = [ + ...HumanMni152Jba29Hoc1Left.loaders, + async () => { + const regions = await getJba29Regions() + const hoc1left = regions.find(r => /left/i.test(r.name) && /hoc1/i.test(r.name)) + return { + hoc1left + } + } +] diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.style.css b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.style.css new file mode 100644 index 0000000000000000000000000000000000000000..a897b1ea9ca28e94e43c28f32593fa18fa3426d9 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.style.css @@ -0,0 +1,12 @@ +.vanishing-border +{ + padding: 16px; + margin: -16px!important; +} + +.feature-list-container +{ + max-height: 16rem; + overflow-x: hidden; + overflow-y: scroll; +} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html new file mode 100644 index 0000000000000000000000000000000000000000..d4a9a79d17698288b49f115c83d895459327bd20 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html @@ -0,0 +1,187 @@ +<ng-template #headerTmpl> + <ng-content select="[header]"></ng-content> +</ng-template> + +<ng-template [ngIf]="region"> + + <mat-card class="mat-elevation-z4"> + <div + [style.backgroundColor]="regionRgbString" + class="vanishing-border" + [ngClass]="{ + 'darktheme': regionDarkmode === true, + 'lighttheme': regionDarkmode === false + }"> + + <ng-template [ngTemplateOutlet]="headerTmpl"></ng-template> + + <mat-card-title class="sxplr-custom-cmp text"> + {{ region.name }} + </mat-card-title> + + + <!-- subtitle on what it is --> + <mat-card-subtitle class="d-inline-flex align-items-center flex-wrap"> + <mat-icon fontSet="fas" fontIcon="fa-brain"></mat-icon> + <span> + Brain region + </span> + + <!-- origin datas format --> + + <mat-divider vertical="true" class="sxplr-pl-2 h-2rem"></mat-divider> + + <!-- position --> + <button mat-icon-button *ngIf="regionPosition" + (click)="navigateTo(regionPosition)" + [matTooltip]="ARIA_LABELS.GO_TO_REGION_CENTROID + ': ' + (regionPosition | numbers | addUnitAndJoin : 'mm')"> + <mat-icon fontSet="fas" fontIcon="fa-map-marked-alt"> + </mat-icon> + </button> + + <!-- explore doi --> + <a *ngFor="let doi of dois" + [href]="doi | parseDoi" + [matTooltip]="ARIA_LABELS.EXPLORE_DATASET_IN_KG" + target="_blank" + mat-icon-button> + <i class="fas fa-external-link-alt"></i> + </a> + + </mat-card-subtitle> + + </div> + </mat-card> + + + <!-- kg regional features list --> + <ng-template #kgRegionalFeatureList> + <div sxplr-sapiviews-core-region-regional-feature + [sxplr-sapiviews-core-region-atlas]="atlas" + [sxplr-sapiviews-core-region-template]="template" + [sxplr-sapiviews-core-region-parcellation]="parcellation" + [sxplr-sapiviews-core-region-region]="region" + #rfDir="sapiViewsRegionalFeature" + class="feature-list-container" + > + + <spinner-cmp *ngIf="rfDir.busy$ | async"></spinner-cmp> + + <sxplr-sapiviews-features-entry-list-item + *ngFor="let feat of rfDir.listOfFeatures$ | async | orderFilterFeatures" + [sxplr-sapiviews-features-entry-list-item-feature]="feat" + (click)="handleRegionalFeatureClicked(feat)"> + </sxplr-sapiviews-features-entry-list-item> + </div> + + </ng-template> + + <ng-template #regionDesc> + <markdown-dom class="sxplr-muted" [markdown]="region?.versionInnovation || 'No description provided.'"> + </markdown-dom> + </ng-template> + + <mat-accordion class="d-block mt-2"> + + <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { + title: CONST.DESCRIPTION, + iconClass: 'fas fa-info', + content: regionDesc, + desc: '', + iconTooltip: 'Description', + iavNgIf: !!region?.versionInnovation + }"> + + </ng-container> + + <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { + title: CONST.REGIONAL_FEATURES, + iconClass: 'fas fa-database', + content: kgRegionalFeatureList, + desc: '', + iconTooltip: 'Regional Features', + iavNgIf: true + }"> + </ng-container> + + <!-- connectivity --> + <ng-template #sxplrSapiviewsFeaturesConnectivityBrowser> + <sxplr-sapiviews-features-connectivity-browser + class="pe-all flex-shrink-1" + [region]="region" + [types]="hasConnectivityDirective.availableModalities" + [defaultProfile]="hasConnectivityDirective.defaultProfile" + [sxplr-sapiviews-features-connectivity-browser-atlas]="atlas" + [sxplr-sapiviews-features-connectivity-browser-parcellation]="parcellation" + [accordionExpanded]="expandedPanel === CONST.CONNECTIVITY" + > + </sxplr-sapiviews-features-connectivity-browser> + </ng-template> + + <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { + title: CONST.CONNECTIVITY, + iconClass: 'fab fa-connectdevelop', + content: sxplrSapiviewsFeaturesConnectivityBrowser, + desc: hasConnectivityDirective.connectivityNumber, + iconTooltip: hasConnectivityDirective.connectivityNumber + 'Connections', + iavNgIf: hasConnectivityDirective.hasConnectivity + }"> + </ng-container> + + <div sxplr-sapiviews-features-connectivity-check + [sxplr-sapiviews-features-connectivity-check-atlas]="atlas" + [sxplr-sapiviews-features-connectivity-check-parcellation]="parcellation" + [region]="region" + #hasConnectivityDirective="hasConnectivityDirective"> + </div> + + </mat-accordion> + +</ng-template> + +<!-- expansion tmpl --> +<ng-template #ngMatAccordionTmpl + let-title="title" + let-desc="desc" + let-iconClass="iconClass" + let-iconTooltip="iconTooltip" + let-iavNgIf="iavNgIf" + let-content="content"> + + <mat-expansion-panel + [expanded]="activePanelTitles$ | async | includes : title" + [attr.data-opened]="expansionPanel.expanded" + [attr.data-mat-expansion-title]="title" + (closed)="handleExpansionPanelClosedEv(title)" + (afterExpand)="handleExpansionPanelAfterExpandEv(title)" + hideToggle + *ngIf="iavNgIf" + #expansionPanel="matExpansionPanel"> + + <mat-expansion-panel-header> + + <!-- title --> + <mat-panel-title> + {{ title }} + </mat-panel-title> + + <!-- desc + icon --> + <mat-panel-description class="sxplr-d-flex sxplr-align-items-center sxplr-justify-content-end" + [matTooltip]="iconTooltip"> + <span class="mr-3">{{ desc }}</span> + <span class="accordion-icon d-inline-flex justify-content-center"> + <i [class]="iconClass"></i> + </span> + </mat-panel-description> + + </mat-expansion-panel-header> + + <!-- content --> + <ng-template matExpansionPanelContent> + <ng-container *ngTemplateOutlet="content; context: { + expansionPanel: expansionPanel + }"> + </ng-container> + </ng-template> + </mat-expansion-panel> +</ng-template> diff --git a/src/components/markdown/markdown.style.css b/src/atlasComponents/sapiViews/core/rich/index.ts similarity index 100% rename from src/components/markdown/markdown.style.css rename to src/atlasComponents/sapiViews/core/rich/index.ts diff --git a/src/atlasComponents/sapiViews/core/rich/module.ts b/src/atlasComponents/sapiViews/core/rich/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f946335ee020a073f3ff6f74124360513db6123 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/rich/module.ts @@ -0,0 +1,34 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { SxplrFlatHierarchyModule } from "src/components/flatHierarchy"; +import { AngularMaterialModule } from "src/sharedModules"; +import { UtilModule } from "src/util"; +import { SapiViewsUtilModule } from "../../util"; +import { SapiViewsCoreRegionModule } from "../region"; +import { HighlightPipe } from "./regionsHierarchy/highlight.pipe"; +import { SapiViewsCoreRichRegionsHierarchy } from "./regionsHierarchy/regionsHierarchy.component"; +import { SapiViewsCoreRichRegionListSearch } from "./regionsListSearch/regionListSearch.component"; + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule, + ReactiveFormsModule, + SapiViewsCoreRegionModule, + SxplrFlatHierarchyModule, + SapiViewsUtilModule, + UtilModule, + ], + declarations: [ + SapiViewsCoreRichRegionListSearch, + SapiViewsCoreRichRegionsHierarchy, + HighlightPipe, + ], + exports: [ + SapiViewsCoreRichRegionListSearch, + SapiViewsCoreRichRegionsHierarchy, + ] +}) + +export class SapiViewsCoreRichModule{} \ No newline at end of file diff --git a/src/atlasComponents/parcellation/regionHierachy/filterNameBySearch.pipe.ts b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/filterByRegex.pipe.ts similarity index 77% rename from src/atlasComponents/parcellation/regionHierachy/filterNameBySearch.pipe.ts rename to src/atlasComponents/sapiViews/core/rich/regionsHierarchy/filterByRegex.pipe.ts index f421f0b659d60be9fece6ef87e45b3bb9fb42168..5b1aaa4d71d1c4791cb09d83dbc5b00100523502 100644 --- a/src/atlasComponents/parcellation/regionHierachy/filterNameBySearch.pipe.ts +++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/filterByRegex.pipe.ts @@ -1,15 +1,16 @@ import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ - name : 'filterNameBySearch', + name : 'filterByRegex', + pure: true, }) -export class FilterNameBySearch implements PipeTransform { +export class FilterByRegexPipe implements PipeTransform { public transform(searchFields: string[], searchTerm: string) { try { return searchFields.some(searchField => new RegExp(searchTerm, 'i').test(searchField)) } catch (e) { - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping // CC0 or MIT return searchFields.some(searchField => new RegExp(searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).test(searchField)) } diff --git a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/highlight.pipe.ts b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/highlight.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..73f3e3ddced7b1ab08f3c53e31e25b11f30d93d1 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/highlight.pipe.ts @@ -0,0 +1,28 @@ +import { Pipe, PipeTransform, SecurityContext } from "@angular/core"; +import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; + +@Pipe({ + name: 'hightlightPipe', + pure: true +}) + +export class HighlightPipe implements PipeTransform { + + constructor(private sanitizer: DomSanitizer){} + + transform(input: string, highlight: string = ''): SafeHtml { + let regex: RegExp + if (highlight === '') return input + try { + regex = new RegExp(highlight, 'i') + } catch (e) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + // CC0 or MIT + regex = new RegExp(highlight.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + } + return this.sanitizer.sanitize( + SecurityContext.HTML, + input.replace(regex, s => `<mark>${s}</mark>`) + ) + } +} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionTreeFilter.pipe.ts b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionTreeFilter.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..b125458a82355e482fd9340ca3631d9aa198fded --- /dev/null +++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionTreeFilter.pipe.ts @@ -0,0 +1,17 @@ +import { Pipe, PipeTransform } from "@angular/core"; + +@Pipe({ + name : 'regionTreeFilter', + pure: true +}) + +export class RegionTreeFilterPipe implements PipeTransform { + public transform<T>(array: T[], filterFn: (item: T) => boolean, getChildren: (item: T) => T[]): T[] { + const transformSingle = (item: T): boolean => + filterFn(item) || (getChildren(item) || []).some(transformSingle) + + return array + ? array.filter(transformSingle) + : [] + } +} diff --git a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..e47751ef496636389b5e6865da40dee2251066bf --- /dev/null +++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.component.ts @@ -0,0 +1,98 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewChild } from "@angular/core"; +import { FormControl } from "@angular/forms"; +import { Subscription } from "rxjs"; +import { debounceTime, distinctUntilChanged, filter, startWith } from "rxjs/operators"; +import { SapiRegionModel } from "src/atlasComponents/sapi/type"; +import { SxplrFlatHierarchyTreeView } from "src/components/flatHierarchy/treeView/treeView.component"; +import { FilterByRegexPipe } from "./filterByRegex.pipe"; +import { RegionTreeFilterPipe } from "./regionTreeFilter.pipe"; + +const regionTreeFilterPipe = new RegionTreeFilterPipe() +const filterByRegexPipe = new FilterByRegexPipe() + +@Component({ + selector: `sxplr-sapiviews-core-rich-regionshierarchy`, + templateUrl: './regionsHierarchy.template.html', + styleUrls: [ + `./regionsHierarchy.style.css` + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) + +export class SapiViewsCoreRichRegionsHierarchy { + + static IsParent(region: SapiRegionModel, parentRegion: SapiRegionModel) { + return region.hasParent?.some(parent => parent['@id'] === parentRegion["@id"]) + } + + static FilterRegions(regions: SapiRegionModel[], searchTerm: string): SapiRegionModel[]{ + if (searchTerm === '' || !searchTerm) { + return regions + } + return regionTreeFilterPipe.transform( + regions, + region => filterByRegexPipe.transform([ region.name ], searchTerm), + region => regions.filter(child => SapiViewsCoreRichRegionsHierarchy.IsParent(child, region)) + ) + } + + @Input('sxplr-sapiviews-core-rich-regionshierarchy-accent-regions') + accentedRegions: SapiRegionModel[] = [] + + @Input('sxplr-sapiviews-core-rich-regionshierarchy-placeholder') + placeholderText: string = 'Search all regions' + + passedRegions: SapiRegionModel[] = [] + + private _regions: SapiRegionModel[] = [] + get regions(){ + return this._regions + } + @Input('sxplr-sapiviews-core-rich-regionshierarchy-regions') + set regions(val: SapiRegionModel[]){ + this._regions = val + this.passedRegions = SapiViewsCoreRichRegionsHierarchy.FilterRegions( + this._regions, + this.searchTerm + ) + } + + @Output('sxplr-sapiviews-core-rich-regionshierarchy-region-select') + nodeClicked = new EventEmitter<SapiRegionModel>() + + @ViewChild(SxplrFlatHierarchyTreeView) + treeView: SxplrFlatHierarchyTreeView<SapiRegionModel> + + isParent = SapiViewsCoreRichRegionsHierarchy.IsParent + + searchFormControl = new FormControl() + + searchTerm: string + + constructor( + private cdr: ChangeDetectorRef + ){ + this.subs.push( + this.searchFormControl.valueChanges.pipe( + startWith(''), + distinctUntilChanged(), + debounceTime(320), + /** + * empty string should trigger search + * showing all regions + */ + filter(val => val === '' || !!val) + ).subscribe(val => { + this.searchTerm = val + this.passedRegions = SapiViewsCoreRichRegionsHierarchy.FilterRegions( + this._regions, + this.searchTerm + ) + this.cdr.markForCheck() + }) + ) + } + + private subs: Subscription[] = [] + +} diff --git a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.stories.ts b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..8dbfce6e38bba1201cdd7d94adc310c0381cf1de --- /dev/null +++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.stories.ts @@ -0,0 +1,141 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Component, EventEmitter, TemplateRef, ViewChild } from "@angular/core" +import { MatDialog } from "@angular/material/dialog" +import { action } from "@storybook/addon-actions" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { atlasId, provideDarkTheme, getParcRegions, getAtlas, getParc } from "src/atlasComponents/sapi/stories.base" +import { SapiRegionModel } from "src/atlasComponents/sapi/type" +import { AngularMaterialModule } from "src/sharedModules" +import { SapiViewsCoreRichModule } from "../module" + +@Component({ + selector: `region-tree-hierarchy-wrapper`, + template: ` + <ng-template #matDialog let-data> + <div class="sxplr-w-100 sxplr-h-100"> + <sxplr-sapiviews-core-rich-regionshierarchy + class="sxplr-w-100 sxplr-h-100" + [sxplr-sapiviews-core-rich-regionshierarchy-placeholder]="data.placeholder" + [sxplr-sapiviews-core-rich-regionshierarchy-regions]="data.regions" + (sxplr-sapiviews-core-rich-regionshierarchy-region-select)="nodeClicked.emit($event)" + > + </sxplr-sapiviews-core-rich-regionshierarchy> + </div> + </ng-template> + <mat-accordion> + <mat-expansion-panel *ngFor="let item of regionsDict | keyvalue"> + + <mat-expansion-panel-header> + {{ item.key }} + </mat-expansion-panel-header> + + <ng-template matExpansionPanelContent> + <button mat-button + (click)="openDialog(moreItem.key, moreItem.value)" + *ngFor="let moreItem of item.value | keyvalue"> + {{ moreItem.key }} + </button> + </ng-template> + + </mat-expansion-panel> + </mat-accordion> + `, + styles: [ + `` + ] +}) + +class RegionTreeHierarchyWrapper { + regionsDict: Record<string, Record<string, SapiRegionModel[]>> = {} + nodeClicked = new EventEmitter() + + @ViewChild('matDialog', { read: TemplateRef }) + dialogTmpl: TemplateRef<any> + + openDialog(parcellationName: string, regions: SapiRegionModel[]){ + this.dialog.open(this.dialogTmpl, { + height: '90vh', + width: '90vw', + data: { + placeholder: `Search regions in ${parcellationName} ...`, + regions + } + }) + } + + constructor(private dialog: MatDialog){ + + } +} + +export default { + component: RegionTreeHierarchyWrapper, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreRichModule, + AngularMaterialModule, + ], + providers: [ + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<RegionTreeHierarchyWrapper> = (args: RegionTreeHierarchyWrapper, { loaded, parameters }) => { + const { + regionsDict + } = loaded + + const { + + } = parameters + + const { + + } = args + + return ({ + props: { + regionsDict, + nodeClicked: { + emit: action("nodeClicked") + } + }, + }) +} +Template.loaders = [] + +const asyncLoader = async () => { + const regionsDict: Record<string, Record<string, SapiRegionModel[]>> = {} + for (const species in atlasId) { + const atlasDetail = await getAtlas(atlasId[species]) + regionsDict[species] = {} + + for (const parc of atlasDetail.parcellations) { + const parcDetail = await getParc(atlasDetail['@id'], parc['@id']) + regionsDict[species][parcDetail.name] = await getParcRegions(atlasDetail['@id'], parc['@id'], atlasDetail.spaces[0]["@id"] ) + } + } + + return { + regionsDict + } +} + +export const Default = Template.bind({}) +Default.loaders = [ + async () => { + const { + regionsDict + } = await asyncLoader() + return { + regionsDict + } + } +] diff --git a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.style.css b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.style.css new file mode 100644 index 0000000000000000000000000000000000000000..669b600a34e848c7a12a1c1657f38db8617d7553 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.style.css @@ -0,0 +1,16 @@ +.region-tmpl +{ + text-overflow: clip; + white-space: nowrap; +} + +:host +{ + display: flex; + flex-direction: column; +} + +sxplr-flat-hierarchy-tree-view +{ + flex: 0px 1 1; +} diff --git a/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.template.html b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.template.html new file mode 100644 index 0000000000000000000000000000000000000000..9c085e07cef508f4de0506fb740705c341616790 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/rich/regionsHierarchy/regionsHierarchy.template.html @@ -0,0 +1,35 @@ +<form class="sxplr-custom-cmp text sxplr-w-100"> + <mat-form-field class="sxplr-w-100"> + <input + [placeholder]="placeholderText" + type="text" + matInput + name="searchTerm" + [formControl]="searchFormControl" + autocomplete="off"> + + <!-- search input suffix --> + <div matSuffix> + <ng-content select="[search-input-suffix]"></ng-content> + </div> + + </mat-form-field> +</form> + +<ng-template #tmplRef let-region> + <div class="mat-body sxplr-d-flex sxplr-align-items-center sxplr-h-100 region-tmpl" + [ngClass]="{ + 'sxplr-custom-cmp accent': accentedRegions | includes : region + }" + [innerHTML]="region.name | hightlightPipe : searchTerm"> + </div> +</ng-template> + +<sxplr-flat-hierarchy-tree-view + [sxplr-flat-hierarchy-nodes]="passedRegions" + [sxplr-flat-hierarchy-is-parent]="isParent" + [sxplr-flat-hierarchy-render-node-tmpl]="tmplRef" + [sxplr-flat-hierarchy-tree-view-expand-on-init]="true" + sxplr-flat-hierarchy-tree-view-lineheight="24" + (sxplr-flat-hierarchy-tree-view-node-clicked)="nodeClicked.emit($event)"> +</sxplr-flat-hierarchy-tree-view> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..58be89382e45d671cca1997a32e214c1bf7bb21c --- /dev/null +++ b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.component.ts @@ -0,0 +1,77 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, TemplateRef } from "@angular/core"; +import { SapiRegionModel } from "src/atlasComponents/sapi/type"; +import { ARIA_LABELS } from "common/constants" +import { FormControl } from "@angular/forms"; +import { debounceTime, distinctUntilChanged, map, startWith } from "rxjs/operators"; +import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete"; + +/** + * Filter function, which determines whether the region will be included in the list of autocompleted search. + * Ideally, only the selectable regions are included in the result. + * + * @param region input region + * @returns {boolean} whether or not to include the region in the list search + */ +const filterRegionForListSearch = (region: SapiRegionModel): boolean => { + const visualizedIn = region.hasAnnotation?.visualizedIn + return !!visualizedIn +} + +const filterRegionViaSearch = (searchTerm: string) => (region:SapiRegionModel) => { + return region.name.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase()) +} + +@Component({ + selector: `sxplr-sapiviews-core-rich-regionlistsearch`, + templateUrl: './regionListSearch.template.html', + styleUrls: [ + `./regionListSearch.style.css` + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) + +export class SapiViewsCoreRichRegionListSearch { + + ARIA_LABELS = ARIA_LABELS + + private _regions: SapiRegionModel[] = [] + get regions(){ + return this._regions + } + @Input('sxplr-sapiviews-core-rich-regionlistsearch-regions') + set regions(val: SapiRegionModel[]) { + this._regions = val.filter(filterRegionForListSearch) + } + + @Input('sxplr-sapiviews-core-rich-regionlistsearch-region-template-ref') + regionTemplateRef: TemplateRef<any> + + @Input('sxplr-sapiviews-core-rich-regionlistsearch-current-search') + currentSearch: string = '' + + @Output('sxplr-sapiviews-core-rich-regionlistsearch-region-select') + onOptionSelected = new EventEmitter<SapiRegionModel>() + + public searchFormControl = new FormControl() + + public autocompleteList$ = this.searchFormControl.valueChanges.pipe( + startWith(''), + distinctUntilChanged(), + debounceTime(160), + map((searchTerm: string | SapiRegionModel) => { + if (typeof searchTerm === "string") { + return this.regions.filter(filterRegionViaSearch(searchTerm)).slice(0,5) + } + return [] + }) + ) + + displayFn(region: SapiRegionModel){ + return region?.name || '' + } + + optionSelected(opt: MatAutocompleteSelectedEvent) { + const selectedRegion = opt.option.value as SapiRegionModel + this.onOptionSelected.emit(selectedRegion) + } +} diff --git a/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.stories.ts b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c63c54de2b32ef0bae180e56ab59639bafa0fa6 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.stories.ts @@ -0,0 +1,139 @@ +import { HttpClientModule } from "@angular/common/http" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { action } from "@storybook/addon-actions" +import { SAPI } from "src/atlasComponents/sapi" +import { atlasId, provideDarkTheme, getParcRegions, parcId, spaceId } from "src/atlasComponents/sapi/stories.base" +import { SapiViewsCoreRichModule } from "../module" +import { SapiViewsCoreRichRegionListSearch } from "./regionListSearch.component" +import { Pipe, PipeTransform, SecurityContext } from "@angular/core" +import { SapiRegionModel } from "src/atlasComponents/sapi/type" +import { DomSanitizer, SafeHtml } from "@angular/platform-browser" + +@Pipe({ + name: 'regionTmplPipe', + pure: true +}) + +class RegionTmplPipe implements PipeTransform { + constructor(private sanitizer: DomSanitizer){ + + } + public transform(region: SapiRegionModel) { + console.log( + region, + (region?.name || '').indexOf("left") >= 0 + ) + return this.sanitizer.bypassSecurityTrustHtml( + (region?.name || '').includes("left") + ? `<i class="fas fa-square"></i>` + : `<i class="fas fa-check-square"></i>` + ) + } +} + +export default { + component: SapiViewsCoreRichRegionListSearch, + decorators: [ + moduleMetadata({ + imports: [ + HttpClientModule, + SapiViewsCoreRichModule, + ], + providers: [ + SAPI, + ...provideDarkTheme, + ], + declarations: [ + RegionTmplPipe, + ] + }) + ], +} as Meta + +const actionData = { + onOptionSelected: { emit: action('onOptionSelected')} +} + +const Template: Story<SapiViewsCoreRichRegionListSearch> = (args: SapiViewsCoreRichRegionListSearch, { loaded, parameters }) => { + const { + regions + } = loaded + const { + contentProjection, + tmplRef + } = parameters + const template = ` + ${tmplRef ? ('<ng-template #tmplref let-region>' + tmplRef + '</ng-template>') : ''} + <sxplr-sapiviews-core-rich-regionlistsearch + ${tmplRef ? '[sxplr-sapiviews-core-rich-regionlistsearch-region-template-ref]="tmplref"' : ''} + > + ${contentProjection || ''} + </sxplr-sapiviews-core-rich-regionlistsearch> + ` + + return ({ + props: { + regions, + onOptionSelected: actionData.onOptionSelected + }, + template + }) +} +Template.loaders = [] + +const asyncLoader = async (atlasId: string, parcId: string, spaceId: string) => { + const regions = await getParcRegions(atlasId, parcId, spaceId) + return { + regions + } +} + +const getContentProjection = ({ searchInputSuffix }) => { + let returnVal = '' + if (searchInputSuffix) { + returnVal += `<span search-input-suffix>${searchInputSuffix}</span>` + } + return returnVal +} + +export const Default = Template.bind({}) +Default.loaders = [ + async () => { + const { regions } = await asyncLoader(atlasId.human, parcId.human.jba29, spaceId.human.mni152) + return { regions } + } +] + +export const InputSuffix = Template.bind({}) +InputSuffix.loaders = [ + async () => { + const { regions } = await asyncLoader(atlasId.human, parcId.human.jba29, spaceId.human.mni152) + return { regions } + } +] +InputSuffix.parameters = { + contentProjection: getContentProjection({ + searchInputSuffix: `SUFFIX` + }) +} + +export const TemplatedRegionSuffix = Template.bind({}) +TemplatedRegionSuffix.loaders = [ + async () => { + const { regions } = await asyncLoader(atlasId.human, parcId.human.jba29, spaceId.human.mni152) + return { regions } + } +] +TemplatedRegionSuffix.parameters = { + tmplRef: `<span [innerHTML]="region | regionTmplPipe"></span>` +} + + +export const Waxholm = Template.bind({}) +Waxholm.loaders = [ + async () => { + const { regions } = await asyncLoader(atlasId.rat, parcId.rat.v4, spaceId.rat.waxholm) + return { regions } + } +] + diff --git a/src/plugin/pluginCsp/pluginCsp.style.css b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.style.css similarity index 100% rename from src/plugin/pluginCsp/pluginCsp.style.css rename to src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.style.css diff --git a/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.template.html b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.template.html new file mode 100644 index 0000000000000000000000000000000000000000..379454ff524357eb9a5717253349a17dac8df6ff --- /dev/null +++ b/src/atlasComponents/sapiViews/core/rich/regionsListSearch/regionListSearch.template.html @@ -0,0 +1,52 @@ +<form class="sxplr-custom-cmp text sxplr-w-100"> + <mat-form-field + class="sxplr-w-100" + floatLabel="never"> + <input + placeholder="Search for regions" + [value]="currentSearch" + #trigger="matAutocompleteTrigger" + type="text" + matInput + name="searchTerm" + [attr.aria-label]="ARIA_LABELS.TEXT_INPUT_SEARCH_REGION" + [formControl]="searchFormControl" + [matAutocomplete]="auto"> + + <!-- search input suffix --> + <div matSuffix iav-stop="click"> + <ng-content select="[search-input-suffix]"></ng-content> + </div> + + </mat-form-field> +</form> + + +<mat-autocomplete + panelWidth="auto" + (optionSelected)="optionSelected($event)" + autoActiveFirstOption + #auto="matAutocomplete" + [displayWith]="displayFn"> + <mat-option + *ngFor="let region of autocompleteList$ | async" + [value]="region"> + + <div class="sxplr-d-flex sxplr-justify-content-space-between"> + + <sxplr-sapiviews-core-region-region-list-item + [sxplr-sapiviews-core-region-region]="region"> + </sxplr-sapiviews-core-region-region-list-item> + + <ng-template + [ngIf]="regionTemplateRef" + [ngTemplateOutlet]="regionTemplateRef" + [ngTemplateOutletContext]="{ + $implicit: region + }"> + + </ng-template> + </div> + + </mat-option> +</mat-autocomplete> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/space/boundingBox.directive.ts b/src/atlasComponents/sapiViews/core/space/boundingBox.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..3d592f3974c5db9b91fb6172fdf05df516b8ad63 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/space/boundingBox.directive.ts @@ -0,0 +1,81 @@ +import { Directive, Input, OnChanges, SimpleChanges } from "@angular/core"; +import { BehaviorSubject, Observable } from "rxjs"; +import { distinctUntilChanged } from "rxjs/operators"; +import { BoundingBoxConcept, SapiAtlasModel, SapiSpaceModel } from "src/atlasComponents/sapi/type"; + +function validateBbox(input: any): boolean { + if (!Array.isArray(input)) return false + if (input.length !== 2) return false + return input.every(el => Array.isArray(el) && el.length === 3 && el.every(val => typeof val === "number")) +} + +@Directive({ + selector: '[sxplr-sapiviews-core-space-boundingbox]', + exportAs: 'sxplrSapiViewsCoreSpaceBoundingBox' +}) + +export class SapiViewsCoreSpaceBoundingBox implements OnChanges{ + @Input('sxplr-sapiviews-core-space-boundingbox-atlas') + atlas: SapiAtlasModel + + @Input('sxplr-sapiviews-core-space-boundingbox-space') + space: SapiSpaceModel + + private _bbox: BoundingBoxConcept + @Input('sxplr-sapiviews-core-space-boundingbox-spec') + set bbox(val: string | BoundingBoxConcept ) { + + if (typeof val === "string") { + try { + const [min, max] = JSON.parse(val) + this._bbox = [min, max] + } catch (e) { + console.warn(`Parse bbox input error`) + } + return + } + if (!validateBbox(val)) { + // console.warn(`Bbox is not string, and validate error`) + return + } + this._bbox = val + } + get bbox(): BoundingBoxConcept { + return this._bbox + } + + private _bbox$: BehaviorSubject<{ + atlas: SapiAtlasModel + space: SapiSpaceModel + bbox: BoundingBoxConcept + }> = new BehaviorSubject({ + atlas: null, + space: null, + bbox: null + }) + + public bbox$: Observable<{ + atlas: SapiAtlasModel + space: SapiSpaceModel + bbox: BoundingBoxConcept + }> = this._bbox$.asObservable().pipe( + distinctUntilChanged( + (prev, curr) => prev.atlas?.["@id"] === curr.atlas?.['@id'] + && prev.space?.["@id"] === curr.space?.["@id"] + && JSON.stringify(prev.bbox) === JSON.stringify(curr.bbox) + ) + ) + + ngOnChanges(): void { + const { + atlas, + space, + bbox + } = this + this._bbox$.next({ + atlas, + space, + bbox + }) + } +} diff --git a/src/atlasComponents/sapiViews/core/space/index.ts b/src/atlasComponents/sapiViews/core/space/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..26c7eed07b1e454d4eac3bcbdf0c77bb9fe4f162 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/space/index.ts @@ -0,0 +1,4 @@ +export { SapiViewsCoreSpaceModule } from "./module" +export { + SapiViewsCoreSpaceBoundingBox +} from "./boundingBox.directive" \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/space/module.ts b/src/atlasComponents/sapiViews/core/space/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..35b96ce90dd005580407c05411d923986e2ad9d4 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/space/module.ts @@ -0,0 +1,24 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ComponentsModule } from "src/components"; +import { SapiViewsCoreSpaceBoundingBox } from "./boundingBox.directive"; +import { PreviewSpaceUrlPipe } from "./previewSpaceUrl.pipe"; +import { SapiViewsCoreSpaceSpaceTile } from "./tile/space.tile.component"; + +@NgModule({ + imports: [ + CommonModule, + ComponentsModule, + ], + declarations: [ + SapiViewsCoreSpaceSpaceTile, + PreviewSpaceUrlPipe, + SapiViewsCoreSpaceBoundingBox, + ], + exports: [ + SapiViewsCoreSpaceSpaceTile, + SapiViewsCoreSpaceBoundingBox, + ] +}) + +export class SapiViewsCoreSpaceModule{} \ No newline at end of file diff --git a/src/atlasComponents/template/getTemplatePreviewUrl.pipe.ts b/src/atlasComponents/sapiViews/core/space/previewSpaceUrl.pipe.ts similarity index 84% rename from src/atlasComponents/template/getTemplatePreviewUrl.pipe.ts rename to src/atlasComponents/sapiViews/core/space/previewSpaceUrl.pipe.ts index c2071c8af1b686baee241c47608695b7a709667d..8bbecdf23e0cf041912d5293c3be12eef5d5f7b6 100644 --- a/src/atlasComponents/template/getTemplatePreviewUrl.pipe.ts +++ b/src/atlasComponents/sapiViews/core/space/previewSpaceUrl.pipe.ts @@ -1,4 +1,5 @@ import { Pipe, PipeTransform } from "@angular/core" +import { SapiSpaceModel } from "src/atlasComponents/sapi" const previewImgMap = new Map([ ['minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588', 'bigbrain.png'], @@ -18,12 +19,12 @@ const previewImgMap = new Map([ ]) @Pipe({ - name: 'getTemplatePreviewUrl', + name: 'previewSpaceUrl', pure: true }) -export class GetTemplatePreviewUrlPipe implements PipeTransform{ - public transform(tile: any){ +export class PreviewSpaceUrlPipe implements PipeTransform{ + public transform(tile: SapiSpaceModel){ const filename = previewImgMap.get(tile['@id']) return filename && `assets/images/atlas-selection/${filename}` } diff --git a/src/atlasComponents/sapiViews/core/space/tile/space.tile.component.ts b/src/atlasComponents/sapiViews/core/space/tile/space.tile.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..e9aaab3bebbdce796155a754857d9b0ff67ce090 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/space/tile/space.tile.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from "@angular/core"; +import { SapiSpaceModel } from "src/atlasComponents/sapi"; + +@Component({ + selector: `sxplr-sapiviews-core-space-tile`, + templateUrl: `./space.tile.template.html`, + styleUrls: [ + `./space.tile.style.css` + ] +}) + +export class SapiViewsCoreSpaceSpaceTile { + @Input('sxplr-sapiviews-core-space-tile-space') + space: SapiSpaceModel + + @Input('sxplr-sapiviews-core-space-tile-selected') + selected: boolean = false + +} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/space/tile/space.tile.stories.ts b/src/atlasComponents/sapiViews/core/space/tile/space.tile.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd385d0f599b21101fdc6021714ad78e921d5178 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/space/tile/space.tile.stories.ts @@ -0,0 +1,116 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Component, Input } from "@angular/core" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI, SapiSpaceModel } from "src/atlasComponents/sapi" +import { atlasId, spaceId, getAtlas, getSpace, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { SapiViewsCoreSpaceModule } from "../module" +import { SapiViewsCoreSpaceSpaceTile } from "./space.tile.component" + +@Component({ + selector: `space-tile-wrapper`, + template: ` + <mat-accordion> + <mat-expansion-panel *ngFor="let item of spaces | keyvalue"> + + <mat-expansion-panel-header> + {{ item.key }} + </mat-expansion-panel-header> + + <ng-template matExpansionPanelContent> + <div class="sxplr-d-inline-flex align-items-start"> + <sxplr-sapiviews-core-space-tile + *ngFor="let spc of item.value" + [sxplr-sapiviews-core-space-tile-space]="spc" + [sxplr-sapiviews-core-space-tile-selected]="spc['@id'] === selected" + class="sxplr-m-2"> + </sxplr-sapiviews-core-space-tile> + </div> + </ng-template> + + </mat-expansion-panel> + </mat-accordion> + `, + styles: [ + `sxplr-sapiviews-core-space-tile { display: inline-block; max-width: 8rem; }` + ] +}) + +class SpaceTileWrapper{ + @Input() + spaces: Record<string, SapiSpaceModel[]> = {} + + @Input() + selected: string = spaceId.human.mni152 +} + +export default { + component: SpaceTileWrapper, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreSpaceModule, + AngularMaterialModule, + ], + providers: [ + SAPI, + ...provideDarkTheme, + ], + declarations: [ + SpaceTileWrapper + ] + }) + ], +} as Meta + +const Template: Story<SapiViewsCoreSpaceSpaceTile> = (args: SapiViewsCoreSpaceSpaceTile, { loaded }) => { + const { + spaces + } = loaded + + return ({ + props: { + ...args, + spaces + } + }) +} +Template.loaders = [ + +] + +const asyncLoader = async () => { + const spaces: Record<string, SapiSpaceModel[]> = {} + for (const species in atlasId) { + const atlasDetail = await getAtlas(atlasId[species]) + spaces[species] = [] + + for (const spc of atlasDetail.spaces) { + const spcDetail = await getSpace(atlasDetail['@id'], spc['@id']) + spaces[species].push(spcDetail) + } + } + + return { + spaces + } +} + +export const Default = Template.bind({}) +Default.args = { + selected: spaceId.human.mni152 +} +Default.loaders = [ + + async () => { + const { + spaces + } = await asyncLoader() + return { + spaces + } + } +] diff --git a/src/plugin/pluginUnit/pluginUnit.template.html b/src/atlasComponents/sapiViews/core/space/tile/space.tile.style.css similarity index 100% rename from src/plugin/pluginUnit/pluginUnit.template.html rename to src/atlasComponents/sapiViews/core/space/tile/space.tile.style.css diff --git a/src/atlasComponents/sapiViews/core/space/tile/space.tile.template.html b/src/atlasComponents/sapiViews/core/space/tile/space.tile.template.html new file mode 100644 index 0000000000000000000000000000000000000000..74ba27c9a7384a0f470b083f4aa5a1df4489401a --- /dev/null +++ b/src/atlasComponents/sapiViews/core/space/tile/space.tile.template.html @@ -0,0 +1,7 @@ +<tile-cmp + *ngIf="space" + [tile-text]="space.fullName" + [tile-image-src]="space | previewSpaceUrl" + [tile-selected]="selected"> + +</tile-cmp> \ No newline at end of file diff --git a/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts similarity index 66% rename from src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts rename to src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts index 6a7449787611327577ac114c5060b7abffb6a9d8..3889c4c3f20621280a66f92f3e37f2a4521414d0 100644 --- a/src/atlasComponents/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts +++ b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts @@ -6,9 +6,9 @@ import {CUSTOM_ELEMENTS_SCHEMA, Directive, Input} from "@angular/core"; import {provideMockActions} from "@ngrx/effects/testing"; import {MockStore, provideMockStore} from "@ngrx/store/testing"; import {Observable, of} from "rxjs"; -import { viewerStateAllRegionsFlattenedRegionSelector, viewerStateOverwrittenColorMapSelector } from "src/services/state/viewerState/selectors"; -import { ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState.store.helper"; import {BS_ENDPOINT} from "src/util/constants"; +import {SAPI} from "src/atlasComponents/sapi"; +import {AngularMaterialModule} from "src/sharedModules"; /** * injecting databrowser module is bad idea @@ -41,17 +41,19 @@ describe('ConnectivityComponent', () => { let datasetList = [ { - ['@id']: 'id1', - src_name: 'id1', - src_info: 'd1', + '@id': 'id1', + name: 'id1', + description: 'd1', kgId: 'kgId1', - kgschema: 'kgschema1' + kgschema: 'kgschema1', + items: [] }, { - ['@id']: 'id2', - src_name: 'id2', - src_info: 'd2', + '@id': 'id2', + name: 'id2', + description: 'd2', kgId: 'kgId2', - kgschema: 'kgschema2' + kgschema: 'kgschema2', + items: [] } ] @@ -59,6 +61,7 @@ describe('ConnectivityComponent', () => { TestBed.configureTestingModule({ imports: [ HttpClientModule, + AngularMaterialModule ], providers: [ provideMockActions(() => actions$), @@ -66,6 +69,15 @@ describe('ConnectivityComponent', () => { { provide: BS_ENDPOINT, useValue: MOCK_BS_ENDPOINT + }, + { + provide: SAPI, + useValue: { + atlases$: of([]), + getSpaceDetail: jasmine.createSpy('getSpaceDetail'), + getParcDetail: jasmine.createSpy('getParcDetail'), + getParcRegions: jasmine.createSpy('getParcRegions'), + } } ], declarations: [ @@ -81,9 +93,8 @@ describe('ConnectivityComponent', () => { beforeEach(() => { const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateAllRegionsFlattenedRegionSelector, []) - mockStore.overrideSelector(viewerStateOverwrittenColorMapSelector, null) - mockStore.overrideSelector(ngViewerSelectorClearViewEntries, []) + // mockStore.overrideSelector(viewerStateOverwrittenColorMapSelector, null) + // mockStore.overrideSelector(ngViewerSelectorClearViewEntries, []) }) it('> component can be created', async () => { @@ -97,17 +108,15 @@ describe('ConnectivityComponent', () => { fixture = TestBed.createComponent(ConnectivityBrowserComponent) component = fixture.componentInstance - component.datasetList = datasetList - - component.changeDataset({value: 'id1'}) + component.defaultProfile = {selectedDataset: datasetList[0]} - expect(component.selectedDataset).toEqual('id1') - expect(component.selectedDatasetDescription).toEqual('d1') + expect(component.selectedDataset['@id']).toEqual('id1') + expect(component.selectedDataset.description).toEqual('d1') - component.changeDataset({value: 'id2'}) + component.defaultProfile = {selectedDataset: datasetList[1]} - expect(component.selectedDataset).toEqual('id2') - expect(component.selectedDatasetDescription).toEqual('d2') + expect(component.selectedDataset['@id']).toEqual('id2') + expect(component.selectedDataset.description).toEqual('d2') }) }); diff --git a/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..23d89f519a537c5a296ba90bd1dfd771bc17a3d5 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.ts @@ -0,0 +1,335 @@ +import {AfterViewInit, Component, ElementRef, OnDestroy, ViewChild, Input, ChangeDetectorRef} from "@angular/core"; +import {select, Store} from "@ngrx/store"; +import {fromEvent, Subscription, BehaviorSubject} from "rxjs"; +import {catchError, take} from "rxjs/operators"; +import {SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel} from "src/atlasComponents/sapi"; +import { atlasAppearance, atlasSelection } from "src/state"; +import {PARSE_TYPEDARRAY} from "src/atlasComponents/sapi/sapi.service"; +import {SapiModalityModel, SapiParcellationFeatureMatrixModel} from "src/atlasComponents/sapi/type"; +import { of } from "rxjs"; +import {CustomLayer} from "src/state/atlasAppearance"; + +@Component({ + selector: 'sxplr-sapiviews-features-connectivity-browser', + templateUrl: './connectivityBrowser.template.html', +}) +export class ConnectivityBrowserComponent implements AfterViewInit, OnDestroy { + + @Input('sxplr-sapiviews-features-connectivity-browser-atlas') + atlas: SapiAtlasModel + + @Input('sxplr-sapiviews-features-connectivity-browser-parcellation') + parcellation: SapiParcellationModel + + /** + * accordion expansion should only toggle the clearviewqueue state + * which should be the single source of truth + * setcolormaps$ is set by the presence/absence of clearviewqueue[CONNECTIVITY_NAME_PLATE] + */ + private _isFirstUpdate = true + + private accordionIsExpanded = false + + @Input() + set accordionExpanded(flag: boolean) { + /** + * ignore first update + */ + if (this._isFirstUpdate) { + this._isFirstUpdate = false + return + } + this.accordionIsExpanded = flag + + if (flag) { + if (this.allRegions.length) { + this.setCustomLayer() + } else { + this.setCustomLayerOnLoad = true + } + } else { + this.removeCustomLayer() + } + + } + + @Input() types: SapiModalityModel[] = [] + + private _defaultProfile + @Input() set defaultProfile(val: any) { + this._defaultProfile = val + this.selectedType = this.types.find(t => t.types.includes(val.type))?.name + this.pageNumber = 1 + this.selectedDataset = this.fixDatasetFormat(val.selectedDataset) + if (val.matrix) this.setMatrixData(val.matrix) + this.numberOfDatasets = val.numberOfDatasets + } + + get defaultProfile() { + return this._defaultProfile + } + + public selectedType: any + + @Input() + set region(val) { + const newRegionName = val && val.name + + if (val.status + && !val.name.includes('left hemisphere') + && !val.name.includes('right hemisphere')) { + this.regionHemisphere = val.status + } + + this.regionName = newRegionName + } + + public regionName: string + public regionHemisphere: string = null + public selectedDataset: any + public connectionsString: string + public pureConnections: { [key: string]: number } + public connectedAreas: BehaviorSubject<ConnectedArea[]> = new BehaviorSubject([]) + public noConnectivityForRegion: boolean + private subscriptions: Subscription[] = [] + public allRegions = [] + private regionIndexInMatrix = -1 + public defaultColorMap: Map<string, Map<number, { red: number, green: number, blue: number }>> + public matrixString: string + public fetching: boolean + public numberOfDatasets: number + public connectivityLayerId = 'connectivity-colormap-id' + private setCustomLayerOnLoad = false + public pageNumber: number + private customLayerEnabled: boolean + + public logDisabled: boolean = true + public logChecked: boolean = true + + @ViewChild('connectivityComponent', {read: ElementRef}) public connectivityComponentElement: ElementRef<any> + @ViewChild('fullConnectivityGrid') public fullConnectivityGridElement: ElementRef<any> + + constructor( + private store$: Store, + private sapi: SAPI, + private changeDetectionRef: ChangeDetectorRef, + ) {} + + public ngAfterViewInit(): void { + this.subscriptions.push( + + this.store$.pipe( + select(atlasSelection.selectors.selectedParcAllRegions) + ).subscribe(flattenedRegions => { + this.defaultColorMap = null + this.allRegions = flattenedRegions + if (this.setCustomLayerOnLoad) { + this.setCustomLayer() + this.setCustomLayerOnLoad = false + } + }), + ) + + this.subscriptions.push( + fromEvent(this.connectivityComponentElement?.nativeElement, 'customToolEvent', {capture: true}) + .subscribe((e: CustomEvent) => { + if (e.detail.name === 'export csv') { + // ToDo Fix in future to use component + const a = document.querySelector('hbp-connectivity-matrix-row'); + (a as any).downloadCSV() + } + }), + fromEvent(this.connectivityComponentElement?.nativeElement, 'connectedRegionClicked', {capture: true}) + .subscribe((e: CustomEvent) => { + this.navigateToRegion(this.getRegionWithName(e.detail.name)) + }), + ) + } + + setCustomLayer() { + if (this.customLayerEnabled) { + this.removeCustomLayer() + } + const map = new Map<SapiRegionModel, number[]>() + const areas = this.connectedAreas.value + for (const region of this.allRegions) { + const area = areas.find(a => a.name === region.name) + if (area) { + map.set(region, Object.values(area.color)) + } else { + map.set(region, [255,255,255,0.1]) + } + } + this.customLayerEnabled = true + const customLayer: CustomLayer = { + clType: 'customlayer/colormap', + id: this.connectivityLayerId, + colormap: map + } + + this.store$.dispatch( + atlasAppearance.actions.addCustomLayer({customLayer}) + ) + } + + removeCustomLayer() { + this.store$.dispatch( + atlasAppearance.actions.removeCustomLayer({ + id: this.connectivityLayerId + }) + ) + } + + selectType(typeName) { + this.selectedType = typeName + this.pageNumber = 1 + this.loadDataset() + } + + datasetSliderChanged(pageNumber) { + this.pageNumber = pageNumber + this.loadDataset() + } + + loadDataset() { + this.fetching = true + const type = this.types.find(t => t.name === this.selectedType).types[0] + return this.sapi.getParcellation(this.atlas["@id"], this.parcellation["@id"]) + .getFeatures({type, page: this.pageNumber, size: 1},) + .pipe( + take(1), + catchError(() => { + this.fetching = false + return of(null) + }) + ).subscribe((res: any) => { + if (res && res.items) { + if (res.total !== this.numberOfDatasets) { + this.numberOfDatasets = res.total + } + this.selectedDataset = this.fixDatasetFormat(res.items[0]) + this.fetchConnectivity() + } + }) + } + + // ToDo this temporary fix is for the bug existing on siibra api https://github.com/FZJ-INM1-BDA/siibra-api/issues/100 + private fixDatasetFormat = (ds) => ds.name.includes('{')? ({ + ...ds, + name: ds.name.substr(0, ds.name.indexOf('{')), + dataset: JSON.parse(ds.name.substring(ds.name.indexOf('{')).replace(/'/g, '"')) + }) : ds + + + fetchConnectivity() { + this.sapi.getParcellation(this.atlas["@id"], this.parcellation["@id"]).getFeatureInstance(this.selectedDataset['@id']) + .pipe(catchError(() => { + this.fetching = false + return of(null) + })) + .subscribe(ds=> { + this.setMatrixData(ds) + this.fetching = false + }) + } + + setMatrixData(data) { + const matrixData = data as SapiParcellationFeatureMatrixModel + this.regionIndexInMatrix = (matrixData.columns as Array<string>).findIndex(md => md === this.regionName) + if (this.regionIndexInMatrix < 0) { + this.fetching = false + this.noConnectivityForRegion = true + this.changeDetectionRef.detectChanges() + return + } else if (this.noConnectivityForRegion) { + this.noConnectivityForRegion = false + } + this.sapi.processNpArrayData<PARSE_TYPEDARRAY.RAW_ARRAY>(matrixData.matrix, PARSE_TYPEDARRAY.RAW_ARRAY) + .then(matrix => { + const regionProfile = matrix.rawArray[this.regionIndexInMatrix] + + const maxStrength = Math.max(...regionProfile) + this.logChecked = maxStrength > 1 + this.logDisabled = maxStrength <= 1 + + const areas = regionProfile.reduce((p, c, i) => ({...p, [matrixData.columns[i]]: c}), {}) + this.pureConnections = areas + this.connectionsString = JSON.stringify(areas) + this.connectedAreas.next(this.formatConnections(areas)) + this.setCustomLayer() + + this.matrixString = JSON.stringify(matrixData.columns.map((mc, i) => ([mc, ...matrix.rawArray[i]]))) + this.changeDetectionRef.detectChanges() + + }) + } + + + changeLog(checked: boolean) { + this.logChecked = checked + this.connectedAreas.next(this.formatConnections(this.pureConnections)) + this.connectivityComponentElement.nativeElement.toggleShowLog() + this.setCustomLayer() + } + + //ToDo navigateRegion action does not work any more + navigateToRegion(region: SapiRegionModel) { + this.store$.dispatch( + atlasSelection.actions.navigateToRegion({ + region + }) + ) + } + + getRegionWithName(region: string) { + return this.allRegions.find(r => r.name === region) + } + + exportConnectivityProfile() { + const a = document.querySelector('hbp-connectivity-matrix-row'); + (a as any).downloadCSV() + } + + public exportFullConnectivity() { + this.fullConnectivityGridElement?.nativeElement['downloadCSV']() + } + + public ngOnDestroy(): void { + this.removeCustomLayer() + this.subscriptions.forEach(s => s.unsubscribe()) + } + + private formatConnections(areas: { [key: string]: number }) { + const cleanedObj = Object.keys(areas) + .map(key => ({name: key, numberOfConnections: areas[key]})) + .filter(f => f.numberOfConnections > 0) + .sort((a, b) => +b.numberOfConnections - +a.numberOfConnections) + + const logMax = this.logChecked ? Math.log(cleanedObj[0].numberOfConnections) : cleanedObj[0].numberOfConnections + const colorAreas = [] + + cleanedObj.forEach((a) => { + colorAreas.push({ + ...a, + color: { + r: this.colormap_red((this.logChecked ? Math.log(a.numberOfConnections) : a.numberOfConnections) / logMax ), + g: this.colormap_green((this.logChecked ? Math.log(a.numberOfConnections) : a.numberOfConnections) / logMax ), + b: this.colormap_blue((this.logChecked ? Math.log(a.numberOfConnections) : a.numberOfConnections) / logMax ) + }, + }) + }) + return colorAreas + } + private clamp = val => Math.round(Math.max(0, Math.min(1.0, val)) * 255) + private colormap_red = x => x < 0.7? this.clamp(4.0 * x - 1.5) : this.clamp(-4.0 * x + 4.5) + private colormap_green = x => x < 0.5? this.clamp(4.0 * x - 0.5) : this.clamp(-4.0 * x + 3.5) + private colormap_blue = x => x < 0.3? this.clamp(4.0 * x + 0.5) : this.clamp(-4.0 * x + 2.5) + +} + +export type ConnectedArea = { + color: {r: number, g: number, b: number} + name: string + numberOfConnections: number +} + diff --git a/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.stories.ts b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..870492b96ef2ef98bc6657c01e3b78652a23c945 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.stories.ts @@ -0,0 +1,168 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import {ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA} from "@angular/core" +import { FormsModule } from "@angular/forms" +import { BrowserAnimationsModule } from "@angular/platform-browser/animations" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI, SapiAtlasModel, SapiParcellationModel } from "src/atlasComponents/sapi" +import { getJba29Features, getHumanAtlas, getJba29 } from "src/atlasComponents/sapi/stories.base" +import {SapiParcellationFeatureMatrixModel, SapiParcellationFeatureModel} from "src/atlasComponents/sapi/type" +import { AngularMaterialModule } from "src/sharedModules" +import {ConnectivityBrowserComponent} from "src/atlasComponents/sapiViews/features/connectivity"; +import {PARSE_TYPEDARRAY} from "src/atlasComponents/sapi/sapi.service"; +import {catchError, take} from "rxjs/operators" +import {of} from "rxjs"; + +@Component({ + selector: 'autoradiograph-wrapper-cmp', + template: ` + + <button mat-button (click)="datasetSliderChanged(1)" class="mb-3">Load Connectivity</button> + + <div class="d-flex">Source: {{regionName}}</div> + + <mat-label> + Dataset + </mat-label> + <mat-slider [min]="1" + [max]="numberOfDatasets" + (change)="datasetSliderChanged($event.value)" + [value]="pageNumber" + thumbLabel + step="1" + class="w-100"> + </mat-slider> + + <hbp-connectivity-matrix-row + #connectivityComponent + [region]="regionName" + [connections]="connectionsString" + showSource="true" + theme="light"> + </hbp-connectivity-matrix-row> + + `, + styles: [ + ` + :host + { + display: block; + max-width: 60rem; + max-height: 60rem; + } + ` + ] +}) +class ExampleConnectivityBrowserWrapper { + atlas: SapiAtlasModel + parcellation: SapiParcellationModel + features: SapiParcellationFeatureModel[] = [] + featureId: string + + regionName: string = 'Area TE 3 (STG) right' + type: string = 'siibra/features/connectivity/streamlineCounts' + pageNumber = 1 + numberOfDatasets = 1 + private regionIndexInMatrix = -1 + public connectionsString: string + + + constructor(private sapi: SAPI, private cdf: ChangeDetectorRef) { + } + + datasetSliderChanged(pageNumber) { + this.pageNumber = pageNumber + this.loadDataset() + } + + loadDataset() { + return this.sapi.getParcellation(this.atlas["@id"], this.parcellation["@id"]) + .getFeatures({type: this.type, page: this.pageNumber, size: 1}) + .pipe( + take(1), + catchError(() => { + return of(null) + }) + ).subscribe((res: any) => { + if (res && res.items) { + this.numberOfDatasets = res.total + this.featureId = res.items[0]['@id'] + this.fetchConnectivity() + } + }) + } + + fetchConnectivity() { + this.sapi.getParcellation(this.atlas["@id"], this.parcellation["@id"]).getFeatureInstance(this.featureId) + .pipe(take(1)) + .subscribe(ds=> { + const matrixData = ds as SapiParcellationFeatureMatrixModel + this.regionIndexInMatrix = (matrixData.columns as Array<string>).findIndex(md => md === this.regionName) + this.sapi.processNpArrayData<PARSE_TYPEDARRAY.RAW_ARRAY>(matrixData.matrix, PARSE_TYPEDARRAY.RAW_ARRAY) + .then(matrix => { + const areas = {} + matrix.rawArray[this.regionIndexInMatrix].forEach((value, i) => { + areas[matrixData.columns[i]] = value + }) + this.connectionsString = JSON.stringify(areas) + this.cdf.detectChanges() + }) + }) + } + + +} + +export default { + component: ExampleConnectivityBrowserWrapper, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + AngularMaterialModule, + HttpClientModule, + BrowserAnimationsModule, + ], + providers: [ + SAPI + ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA, + ], + declarations: [ + ConnectivityBrowserComponent + ] + }) + ], +} as Meta + +const Template: Story<ExampleConnectivityBrowserWrapper> = (args: ExampleConnectivityBrowserWrapper, { loaded }) => { + const { atlas, parc, features } = loaded + return ({ + props: { + ...args, + atlas: atlas, + parcellation: parc, + features + }, + }) +} + +Template.loaders = [ + async () => { + const atlas = await getHumanAtlas() + const parc = await getJba29() + const features = await getJba29Features() + return { + atlas, parc, features + } + } +] + +export const Default = Template.bind({}) +Default.args = { + +} +Default.loaders = [ + ...Template.loaders +] diff --git a/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.template.html b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.template.html new file mode 100644 index 0000000000000000000000000000000000000000..724f131a5ebcb37af35bbee1a3c30f74b57d345a --- /dev/null +++ b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.template.html @@ -0,0 +1,76 @@ +<div class="w-100 h-100 d-block d-flex flex-column sxplr-pb-2"> + <div> + <div *ngIf="types && types.length && selectedType" class="flex-grow-0 flex-shrink-0 d-flex flex-row flex-nowrap"> + <mat-form-field class="flex-grow-1 flex-shrink-1 w-0"> + <mat-label> + Modality + </mat-label> + + <mat-select + [value]="selectedType" + (selectionChange)="selectType($event.value)"> + <mat-option + *ngFor="let type of types" + [matTooltip]="type.name" + [value]="type.name"> + {{ type.name }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + + <div *ngIf="selectedDataset" class="flex-grow-0 flex-shrink-0 d-flex flex-nowrap align-items-center"> + <div class="flex-grow-1 flex-shrink-1 w-0"> + <mat-label> + Dataset + </mat-label> + <mat-slider [min]="1" + [max]="numberOfDatasets" + (change)="datasetSliderChanged($event.value)" + [value]="pageNumber" + thumbLabel + step="1" + class="w-100"> + </mat-slider> + </div> + </div> + + </div> + + <div class="d-flex justify-content-center"> + <mat-spinner *ngIf="fetching"></mat-spinner> + </div> + + <div class="d-flex align-items-center" *ngIf="regionName && !fetching"> + <mat-checkbox class="mr-2" + [checked]="logChecked" + (change)="changeLog($event.checked)" + [disabled]="logDisabled || noConnectivityForRegion">Log 10</mat-checkbox> + <button mat-button [matMenuTriggerFor]="exportMenu" + [disabled]="!connectedAreas.value"> + <i class="fas fa-download mb-2 mr-2"></i> + <label>Export</label> + </button> + </div> + + <hbp-connectivity-matrix-row + #connectivityComponent + *ngIf="regionName && !fetching && !noConnectivityForRegion" + [region]="regionName + (regionHemisphere? ' - ' + regionHemisphere : '')" + [connections]="connectionsString" + show-export="true" hide-export-view="true" + theme="dark"> + </hbp-connectivity-matrix-row> + <div *ngIf="noConnectivityForRegion">No connectivity for the region.</div> + <full-connectivity-grid #fullConnectivityGrid + [matrix]="matrixString" + [datasetName]="selectedDataset?.dataset?.name" + [datasetDescription]="selectedDataset?.dataset?.description" + only-export="true"> + </full-connectivity-grid> + + <mat-menu #exportMenu="matMenu"> + <button mat-menu-item [disabled]="noConnectivityForRegion" (click)="exportConnectivityProfile()">Regional</button> + <button mat-menu-item (click)="exportFullConnectivity()">Dataset</button> + </mat-menu> +</div> diff --git a/src/atlasComponents/sapiViews/features/connectivity/hasConnectivity.directive.ts b/src/atlasComponents/sapiViews/features/connectivity/hasConnectivity.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea99a5b9c4cd5852c2ab7bd4708a7791d2bd5180 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/connectivity/hasConnectivity.directive.ts @@ -0,0 +1,134 @@ +import {Directive, Input, OnDestroy} from "@angular/core"; +import {from, of, Subscription} from "rxjs"; +import {map, switchMap, take} from "rxjs/operators"; +import {HttpClient} from "@angular/common/http"; +import {PARSE_TYPEDARRAY, SAPI} from "src/atlasComponents/sapi/sapi.service"; +import { + SapiAtlasModel, SapiModalityModel, + SapiParcellationFeatureMatrixModel, SapiParcellationFeatureModel, + SapiParcellationModel, + SapiRegionModel +} from "src/atlasComponents/sapi/type"; + +@Directive({ + selector: '[sxplr-sapiviews-features-connectivity-check]', + exportAs: 'hasConnectivityDirective' +}) + +export class HasConnectivity implements OnDestroy { + + private subscriptions: Subscription[] = [] + + @Input('sxplr-sapiviews-features-connectivity-check-atlas') + atlas: SapiAtlasModel + + @Input('sxplr-sapiviews-features-connectivity-check-parcellation') + parcellation: SapiParcellationModel + + private _region: SapiRegionModel + + @Input() + set region(val: SapiRegionModel) { + this._region = val + if (val) { + if (!this.connectivityModalities.length) { + this.waitForModalities = true + } else { + this.checkConnectivity() + } + } else { + this.connectivityNumber = 0 + } + } + + get region() { + return this._region + } + + private regionIndex: number + public hasConnectivity = false + public connectivityNumber = 0 + + private connectivityModalities: SapiModalityModel[] = [] + private waitForModalities = false + public defaultProfile: DefaultProfile + public availableModalities: SapiModalityModel[] = [] + + constructor(private httpClient: HttpClient, + private sapi: SAPI) { + this.sapi.getModalities() + .pipe(map((mod: SapiModalityModel[]) => mod.filter((m: SapiModalityModel) => m.types && m.types.find(t => t.includes('siibra/features/connectivity'))))) + .subscribe(modalities => { + this.connectivityModalities = modalities + if (this.waitForModalities) { + this.waitForModalities = false + this.checkConnectivity() + } + }) + } + + private checkConnectivity() { + if (this.region.name) { + this.connectivityModalities.forEach(m => { + const type = m.types[0] + + this.sapi.getParcellation(this.atlas["@id"], this.parcellation["@id"]) + .getFeatures({type, page: 1, size: 1}) + .pipe( + take(1), + // ToDo remove any type when `SapiParcellationFeatureModel` will be fixed + switchMap((res: SapiParcellationFeatureModel[] | any) => { + if (res && res.items) { + + this.availableModalities.push(m) + const firstDataset = res.items[0] + + if (firstDataset) { + this.hasConnectivity = true + if (!(this.defaultProfile)) { + return this.sapi.getParcellation(this.atlas["@id"], this.parcellation["@id"]) + .getFeatureInstance(firstDataset['@id']) + .pipe(switchMap(inst => { + if (inst) { + this.defaultProfile = { + type, + selectedDataset: firstDataset, + matrix: inst, + numberOfDatasets: res.total + } + + const matrixData = inst as SapiParcellationFeatureMatrixModel + this.regionIndex = (matrixData.columns as Array<string>).findIndex(md => md === this.region.name) + return from(this.sapi.processNpArrayData<PARSE_TYPEDARRAY.RAW_ARRAY>(matrixData.matrix, PARSE_TYPEDARRAY.RAW_ARRAY)) + } + return of(null) + })) + } + } else { + this.hasConnectivity = false + this.connectivityNumber = 0 + } + } + return of(null) + }), + ).subscribe(res => { + if (res && res.rawArray && res.rawArray[this.regionIndex]) { + const connections = res.rawArray[this.regionIndex] + this.connectivityNumber = connections.filter(p => p > 0).length + } + }) + }) + } + } + + ngOnDestroy(){ + while (this.subscriptions.length > 0) this.subscriptions.pop().unsubscribe() + } +} + +type DefaultProfile = { + type: string + selectedDataset: SapiParcellationFeatureModel + matrix: SapiParcellationFeatureModel + numberOfDatasets: number +} \ No newline at end of file diff --git a/src/atlasComponents/connectivity/index.ts b/src/atlasComponents/sapiViews/features/connectivity/index.ts similarity index 61% rename from src/atlasComponents/connectivity/index.ts rename to src/atlasComponents/sapiViews/features/connectivity/index.ts index c9a2e808fc6290283e77ea4bccff8f199eea3ca0..b86944877b50cac06e6b05935fbcf41ac2a67720 100644 --- a/src/atlasComponents/connectivity/index.ts +++ b/src/atlasComponents/sapiViews/features/connectivity/index.ts @@ -1,2 +1,2 @@ export { ConnectivityBrowserComponent } from "./connectivityBrowser/connectivityBrowser.component"; -export { AtlasCmptConnModule } from "./module"; \ No newline at end of file +export { SapiViewsFeatureConnectivityModule } from "./module"; diff --git a/src/atlasComponents/sapiViews/features/connectivity/module.ts b/src/atlasComponents/sapiViews/features/connectivity/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..5402b4039439e6ff775507287712095ef0866104 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/connectivity/module.ts @@ -0,0 +1,29 @@ +import { CommonModule } from "@angular/common"; +import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from "@angular/core"; +import { SAPI } from "src/atlasComponents/sapi"; +import {ConnectivityBrowserComponent} from "src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component"; +import {HasConnectivity} from "src/atlasComponents/sapiViews/features/connectivity/hasConnectivity.directive"; +import {AngularMaterialModule} from "src/sharedModules"; + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule + ], + declarations: [ + ConnectivityBrowserComponent, + HasConnectivity + ], + exports: [ + ConnectivityBrowserComponent, + HasConnectivity + ], + providers: [ + SAPI, + ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA, + ], +}) + +export class SapiViewsFeatureConnectivityModule{} diff --git a/src/atlasComponents/sapiViews/features/entry/entry.component.ts b/src/atlasComponents/sapiViews/features/entry/entry.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..691af83bd53ab4c5937c283fb3ea93056b917910 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/entry/entry.component.ts @@ -0,0 +1,181 @@ +import { Component, Input, OnDestroy } from "@angular/core"; +import { Store } from "@ngrx/store"; +import { TNgAnnotationPoint } from "src/atlasComponents/annotations"; +import { SapiFeatureModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel, CLEANED_IEEG_DATASET_TYPE } from "src/atlasComponents/sapi"; +import { IeegOnFocusEvent, ContactPoint, Electrode, Session, IeegOnDefocusEvent } from "../ieeg"; +import { atlasSelection, annotation } from "src/state" + +@Component({ + selector: 'sxplr-sapiviews-features-entry', + templateUrl: './entry.template.html', + styleUrls: [ + './entry.style.css' + ] +}) + +export class FeatureEntryCmp implements OnDestroy{ + + /** + * in future, hopefully feature detail can be queried with just id, + * and atlas/space/parcellation/region are no longer necessary + */ + @Input('sxplr-sapiviews-features-entry-atlas') + atlas: SapiFeatureModel + + @Input('sxplr-sapiviews-features-entry-space') + space: SapiSpaceModel + + @Input('sxplr-sapiviews-features-entry-parcellation') + parcellation: SapiParcellationModel + + @Input('sxplr-sapiviews-features-entry-region') + region: SapiRegionModel + + @Input('sxplr-sapiviews-features-entry-feature') + feature: SapiFeatureModel + + featureType = { + receptor: "siibra/features/receptor", + ieeg: CLEANED_IEEG_DATASET_TYPE + } + + private addedAnnotations: annotation.UnionAnnotation[] = [] + + ieegOnFocus(ev: IeegOnFocusEvent){ + if (ev.contactPoint) { + /** + * navigate to the point + */ + this.store.dispatch( + atlasSelection.actions.navigateTo({ + navigation: { + position: ev.contactPoint.point.coordinates.map(v => v.value * 1e6) + }, + animation: true + }) + ) + return + } + if (ev.session) { + /** + * + */ + const allInRoiPoints: TNgAnnotationPoint[] = this.getPointsFromSession(ev.session, true) + const allNonInRoiPoints: TNgAnnotationPoint[] = this.getPointsFromSession(ev.session, false) + const annotationsToBeAdded: annotation.UnionAnnotation[] = [] + for (const pt of allInRoiPoints) { + annotationsToBeAdded.push({ + "@id": pt.id, + color: annotation.AnnotationColor.RED, + openminds: { + "@id": pt.id, + "@type": "https://openminds.ebrains.eu/sands/CoordinatePoint", + coordinateSpace: { + "@id": this.space["@id"] + }, + coordinates: pt.point.map(v => { + return { + value: v / 1e6 + } + }) + }, + name: pt.description || "Untitled" + }) + } + for (const pt of allNonInRoiPoints) { + annotationsToBeAdded.push({ + "@id": pt.id, + color: annotation.AnnotationColor.WHITE, + openminds: { + "@id": pt.id, + "@type": "https://openminds.ebrains.eu/sands/CoordinatePoint", + coordinateSpace: { + "@id": this.space["@id"] + }, + coordinates: pt.point.map(v => { + return { + value: v / 1e6 + } + }) + }, + name: pt.description || "Untitled" + }) + } + this.addedAnnotations = annotationsToBeAdded + this.store.dispatch( + annotation.actions.addAnnotations({ + annotations: annotationsToBeAdded + }) + ) + } + } + ieegOnDefocus(ev: IeegOnDefocusEvent){ + if (ev.session) { + const allInRoiPoints: TNgAnnotationPoint[] = this.getPointsFromSession(ev.session, true) + const allNonInRoiPoints: TNgAnnotationPoint[] = this.getPointsFromSession(ev.session, false) + + this.store.dispatch( + annotation.actions.rmAnnotations({ + annotations: [...allInRoiPoints, ...allNonInRoiPoints].map(p => { + return { "@id": p.id } + }) + }) + ) + } + } + + ngOnDestroy(): void { + this.store.dispatch( + annotation.actions.rmAnnotations({ + annotations: this.addedAnnotations + }) + ) + } + + constructor( + private store: Store + ){ + + } + + private getPointsFromSession(session: Session<string>, inRoi: boolean):TNgAnnotationPoint[]{ + const allPoints: TNgAnnotationPoint[] = [] + for (const electrodeKey in session.electrodes) { + const electrode = session.electrodes[electrodeKey] + const points = this.getPointsFromElectrode(electrode, inRoi) + allPoints.push(...points) + } + return allPoints.map(pt => { + return { + ...pt, + id: `${session.sub_id}:${pt.id}` + } + }) + } + + private getPointsFromElectrode(electrode: Electrode<string>, inRoi: boolean): TNgAnnotationPoint[] { + const allPoints: TNgAnnotationPoint[] = [] + for (const ctptKey in electrode.contact_points) { + const ctpt = electrode.contact_points[ctptKey] + if (!inRoi !== !ctpt.inRoi) { + continue + } + const point = this.getPointFromCtPt(ctpt) + allPoints.push(point) + } + return allPoints.map(pt => { + return { + ...pt, + id: `${electrode.electrode_id}:${pt.id}` + } + }) + } + + private getPointFromCtPt(ctpt: ContactPoint<string>): TNgAnnotationPoint { + return { + id: ctpt.id, + point: ctpt.point.coordinates.map(coord => coord.value * 1e6 ) as [number, number, number], + type: 'point' + } + } +} diff --git a/src/services/state/uiState.store.spec.ts b/src/atlasComponents/sapiViews/features/entry/entry.style.css similarity index 100% rename from src/services/state/uiState.store.spec.ts rename to src/atlasComponents/sapiViews/features/entry/entry.style.css diff --git a/src/atlasComponents/sapiViews/features/entry/entry.template.html b/src/atlasComponents/sapiViews/features/entry/entry.template.html new file mode 100644 index 0000000000000000000000000000000000000000..38a00def6eba0d9546e5c729431e4d53a05292bb --- /dev/null +++ b/src/atlasComponents/sapiViews/features/entry/entry.template.html @@ -0,0 +1,22 @@ +<div [ngSwitch]="feature?.['@type']"> + <sxplr-sapiviews-features-receptor-entry + *ngSwitchCase="featureType.receptor" + [sxplr-sapiviews-features-receptor-atlas]="atlas" + [sxplr-sapiviews-features-receptor-parcellation]="parcellation" + [sxplr-sapiviews-features-receptor-template]="space" + [sxplr-sapiviews-features-receptor-region]="region" + [sxplr-sapiviews-features-receptor-featureid]="feature['@id']"> + </sxplr-sapiviews-features-receptor-entry> + <sxplr-sapiviews-features-ieeg-ieegdataset + *ngSwitchCase="featureType.ieeg" + [sxplr-sapiviews-features-ieeg-ieegdataset-atlas]="atlas" + [sxplr-sapiviews-features-ieeg-ieegdataset-space]="space" + [sxplr-sapiviews-features-ieeg-ieegdataset-parcellation]="parcellation" + [sxplr-sapiviews-features-ieeg-ieegdataset-region]="region" + [sxplr-sapiviews-features-ieeg-ieegdataset-feature]="feature" + (sxplr-sapiviews-features-ieeg-ieegdataset-on-focus)="ieegOnFocus($event)" + (sxplr-sapiviews-features-ieeg-ieegdataset-on-defocus)="ieegOnDefocus($event)" + > + + </sxplr-sapiviews-features-ieeg-ieegdataset> +</div> diff --git a/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.component.ts b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..8008affe34a6b783fd1b93ed14ee527279e36d04 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.component.ts @@ -0,0 +1,45 @@ +import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; +import { SapiFeatureModel } from "src/atlasComponents/sapi"; +import { CleanedIeegDataset, CLEANED_IEEG_DATASET_TYPE, SapiDatasetModel, SapiParcellationFeatureMatrixModel, SapiRegionalFeatureReceptorModel, SapiSerializationErrorModel, SapiVOIDataResponse, SxplrCleanedFeatureModel } from "src/atlasComponents/sapi/type"; + +@Component({ + selector: `sxplr-sapiviews-features-entry-list-item`, + templateUrl: `./entryListItem.template.html`, + styleUrls: [ + `./entryListItem.style.css` + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) + +export class SapiViewsFeaturesEntryListItem{ + @Input('sxplr-sapiviews-features-entry-list-item-feature') + feature: SapiFeatureModel | SxplrCleanedFeatureModel + + @Input('sxplr-sapiviews-features-entry-list-item-ripple') + ripple = true + + get label(): string{ + if (!this.feature) return null + const { '@type': type } = this.feature + if ( + type === "https://openminds.ebrains.eu/core/DatasetVersion" || + type === "siibra/features/cells" || + type === "siibra/features/receptor" || + type === "siibra/features/voi" || + type === CLEANED_IEEG_DATASET_TYPE + ) { + return (this.feature as (SapiDatasetModel | SapiRegionalFeatureReceptorModel | SapiVOIDataResponse | CleanedIeegDataset) ).metadata.fullName + } + + if ( + type === "siibra/features/connectivity" || + type === "siibra/features/connectivity/streamlineCounts" + ) { + return (this.feature as SapiParcellationFeatureMatrixModel).name + } + if (type === "spy/serialization-error") { + return (this.feature as SapiSerializationErrorModel).message + } + return "Unknown type" + } +} diff --git a/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.style.css b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.style.css new file mode 100644 index 0000000000000000000000000000000000000000..58f6c43f50f172f58c7933747209ca04ea5530a2 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.style.css @@ -0,0 +1,5 @@ +:host span +{ + overflow: hidden; + white-space: nowrap; +} diff --git a/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.template.html b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.template.html new file mode 100644 index 0000000000000000000000000000000000000000..76f584328b3e46b5a240defb44e98e7cc2abe4a2 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/entryListItem/entryListItem.template.html @@ -0,0 +1,17 @@ +<div matRipple [matRippleDisabled]="!ripple" + class="sxplr-p-2"> + + <mat-chip-list + *ngIf="feature | featureBadgeFlag" + class="sxplr-scale-80 transform-origin-left-center"> + <mat-chip + [color]="feature | featureBadgeColour" + selected> + {{ feature | featureBadgeName }} + </mat-chip> + </mat-chip-list> + + <span class="d-block mat-body"> + {{ label }} + </span> +</div> diff --git a/src/atlasComponents/sapiViews/features/entryListItem/entryListitem.stories.ts b/src/atlasComponents/sapiViews/features/entryListItem/entryListitem.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..a93cd9816a00d9f29567b8a55ecfe481030e8c31 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/entryListItem/entryListitem.stories.ts @@ -0,0 +1,88 @@ +import { CommonModule } from "@angular/common" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SapiFeatureModel } from "src/atlasComponents/sapi" +import { getHoc1RightFeatures, getHoc1RightSpatialFeatures, getJba29Features, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { SapiViewsFeaturesEntryListItem } from "./entryListItem.component" +import { Component, EventEmitter, Input, Output } from "@angular/core" +import { SapiViewsFeaturesModule } from ".." + +@Component({ + selector: `feature-list-item-wrapper`, + template: ` + <mat-card> + <sxplr-sapiviews-features-entry-list-item + *ngFor="let feat of features" + (click)="clicked.emit(feat)" + [sxplr-sapiviews-features-entry-list-item-feature]="feat" + [sxplr-sapiviews-features-entry-list-item-ripple]="ripple"> + </sxplr-sapiviews-features-entry-list-item> + </mat-card> + ` +}) + +class FeatureListItemWrapper { + features: SapiFeatureModel[] = [] + + @Input() + ripple: boolean = true + + @Output() + clicked = new EventEmitter() +} + +export default { + component: FeatureListItemWrapper, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + AngularMaterialModule, + SapiViewsFeaturesModule, + ], + declarations: [], + providers: [ + ...provideDarkTheme, + ], + }) + ], + +} as Meta + +const Template: Story<SapiViewsFeaturesEntryListItem> = (args: SapiViewsFeaturesEntryListItem, { loaded }) => { + const { features } = loaded + return ({ + props: { + ...args, + features + }, + }) +} + + +export const RegionalFeatures = Template.bind({}) +RegionalFeatures.args = { + +} +RegionalFeatures.loaders = [ + async () => { + const features = [ + ...(await getHoc1RightSpatialFeatures()), + ...(await getHoc1RightFeatures()) + ] + return { + features + } + } +] + +export const ParcellationFeatures = Template.bind({}) +ParcellationFeatures.loaders = [ + async () => { + + const features = await getJba29Features() + return { + features + } + } +] \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/featureBadgeColor.pipe.ts b/src/atlasComponents/sapiViews/features/featureBadgeColor.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..474d6a2adfe72257c35a406abde767ab7054f143 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/featureBadgeColor.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { SapiFeatureModel, SxplrCleanedFeatureModel, CLEANED_IEEG_DATASET_TYPE } from "src/atlasComponents/sapi"; + +@Pipe({ + name: 'featureBadgeColour', + pure: true +}) + +export class FeatureBadgeColourPipe implements PipeTransform{ + public transform(regionalFeature: SapiFeatureModel|SxplrCleanedFeatureModel) { + if (regionalFeature['@type'] === "siibra/features/receptor") { + return "accent" + } + if (regionalFeature['@type'] === CLEANED_IEEG_DATASET_TYPE) { + return "primary" + } + return "default" + } +} diff --git a/src/atlasComponents/sapiViews/features/featureBadgeFlag.pipe.ts b/src/atlasComponents/sapiViews/features/featureBadgeFlag.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..986edffac941e522ee27d76dd3ab626aa40b993d --- /dev/null +++ b/src/atlasComponents/sapiViews/features/featureBadgeFlag.pipe.ts @@ -0,0 +1,14 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { SapiFeatureModel, SxplrCleanedFeatureModel, CLEANED_IEEG_DATASET_TYPE } from "src/atlasComponents/sapi"; + +@Pipe({ + name: 'featureBadgeFlag', + pure: true +}) + +export class FeatureBadgeFlagPipe implements PipeTransform{ + public transform(regionalFeature: SapiFeatureModel|SxplrCleanedFeatureModel) { + return regionalFeature['@type'] === "siibra/features/receptor" + || regionalFeature['@type'] === CLEANED_IEEG_DATASET_TYPE + } +} diff --git a/src/atlasComponents/sapiViews/features/featureBadgeName.pipe.ts b/src/atlasComponents/sapiViews/features/featureBadgeName.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..33570440c6aa4c0f601d7171cac61cce7c42e712 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/featureBadgeName.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { SapiFeatureModel, SxplrCleanedFeatureModel, CLEANED_IEEG_DATASET_TYPE } from "src/atlasComponents/sapi"; + +@Pipe({ + name: 'featureBadgeName', + pure: true +}) + +export class FeatureBadgeNamePipe implements PipeTransform{ + public transform(regionalFeature: SapiFeatureModel|SxplrCleanedFeatureModel) { + if (regionalFeature['@type'] === "siibra/features/receptor") { + return "receptor density" + } + if (regionalFeature["@type"] === CLEANED_IEEG_DATASET_TYPE) { + return "IEEG dataset" + } + return null + } +} diff --git a/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.component.ts b/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..de23194ff366b475d57954b3050aa2cf94cfb256 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.component.ts @@ -0,0 +1,113 @@ +import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChanges } from "@angular/core"; +import { BehaviorSubject, forkJoin } from "rxjs"; +import { SAPI } from "src/atlasComponents/sapi/sapi.service"; +import { CleanedIeegDataset, cleanIeegSessionDatasets, SapiAtlasModel, SapiIeegSessionModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi/type"; + +export type Session<SId extends string> = CleanedIeegDataset['sessions'][SId] +export type Electrode<EId extends string> = Session<string>['electrodes'][EId] +export type ContactPoint<CId extends string> = Electrode<string>['contact_points'][CId] + +export type IeegOnFocusEvent = { + contactPoint: ContactPoint<string> + electrode: Electrode<string> + session: Session<string> +} + +export type IeegOnDefocusEvent = { + contactPoint: ContactPoint<string> + electrode: Electrode<string> + session: Session<string> +} + +@Component({ + selector: `sxplr-sapiviews-features-ieeg-ieegdataset`, + templateUrl: `./ieegDataset.template.html`, + styleUrls: [ + `./ieegDataset.style.css` + ] +}) + +export class IEEGDatasetCmp implements OnChanges{ + + @Input('sxplr-sapiviews-features-ieeg-ieegdataset-atlas') + public atlas: SapiAtlasModel + + @Input('sxplr-sapiviews-features-ieeg-ieegdataset-space') + public space: SapiSpaceModel + + @Input('sxplr-sapiviews-features-ieeg-ieegdataset-parcellation') + public parcellation: SapiParcellationModel + + @Input('sxplr-sapiviews-features-ieeg-ieegdataset-region') + public region: SapiRegionModel + + /** + * we must assume that the passed feature does not have the detail flag on + * We need to fetch the + */ + @Input('sxplr-sapiviews-features-ieeg-ieegdataset-feature') + public feature: CleanedIeegDataset + public detailedFeature: CleanedIeegDataset + + @Output('sxplr-sapiviews-features-ieeg-ieegdataset-on-focus') + public onFocus = new EventEmitter<IeegOnFocusEvent>() + + @Output('sxplr-sapiviews-features-ieeg-ieegdataset-on-defocus') + public onDefocus = new EventEmitter<IeegOnDefocusEvent>() + + public busy$ = new BehaviorSubject<boolean>(false) + + ngOnChanges(changes: SimpleChanges): void { + if (!this.feature) { + return + } + if (this.feature && this.feature["@type"] !== "sxplr/cleanedIeegDataset") { + throw new Error(`expected @type to be sxplr-cleaned-ieeg-dataset, but is ${this.feature['@type']}.`) + } + this.busy$.next(true) + + forkJoin( + Object.entries(this.feature.sessions).map(([ key, session ]) => { + return this.sapi.getSpace(this.atlas["@id"], this.space["@id"]).getFeatureInstance(session["@id"], { parcellationId: this.parcellation["@id"], region: this.region.name }) + }) + ).subscribe(feats => { + + const ieegSessions: SapiIeegSessionModel[] = feats.filter(feat => feat["@type"] === "siibra/features/ieegSession") + const features = cleanIeegSessionDatasets(ieegSessions) + const foundFeat = features.find(f => f["@id"] === this.feature["@id"]) + if (foundFeat) { + this.detailedFeature = foundFeat + } + this.busy$.next(false) + }) + } + + public onContactPointClicked(cpt: ContactPoint<string>, ele: Electrode<string>, sess: Session<string>){ + this.onFocus.emit({ + contactPoint: cpt, + electrode: ele, + session: sess + }) + } + + public onPanelOpen(session: Session<string>){ + this.onFocus.emit({ + contactPoint: null, + electrode: null, + session: session + }) + } + + public onPanelClose(session: Session<string>){ + this.onDefocus.emit({ + contactPoint: null, + electrode: null, + session: session + }) + } + + constructor(private sapi: SAPI){ + + } + +} diff --git a/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.stories.ts b/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9f08880e4525ab8c2d9f550abee83bf60c876fc --- /dev/null +++ b/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.stories.ts @@ -0,0 +1,113 @@ +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" +import { getHoc1Right, getHumanAtlas, getJba29, getMni152, provideDarkTheme, getMni152SpatialFeatureHoc1Right } from "src/atlasComponents/sapi/stories.base" +import { SxplrSapiViewsFeaturesIeegModule } from ".." +import { Component } from "@angular/core" +import { cleanIeegSessionDatasets, SapiSpatialFeatureModel } from "src/atlasComponents/sapi/type" +import { action } from "@storybook/addon-actions" + +@Component({ + selector: 'ieeg-entry-wrapper-cmp', + template: ` + <sxplr-sapiviews-features-ieeg-ieegdataset + [sxplr-sapiviews-features-ieeg-ieegdataset-atlas]="atlas" + [sxplr-sapiviews-features-ieeg-ieegdataset-space]="template" + [sxplr-sapiviews-features-ieeg-ieegdataset-parcellation]="parcellation" + [sxplr-sapiviews-features-ieeg-ieegdataset-region]="region" + [sxplr-sapiviews-features-ieeg-ieegdataset-feature]="feature" + (sxplr-sapiviews-features-ieeg-ieegdataset-on-focus)="handleCtptClick($event)" + (sxplr-sapiviews-features-ieeg-ieegdataset-on-defocus)="handleOnDeFocus($event)" + > + </sxplr-sapiviews-features-ieeg-ieegdataset> + `, + styles: [ + ` + :host + { + display: block; + width: 20rem; + } + ` + ] +}) +class EntryWrappercls { + atlas: SapiAtlasModel + template: SapiSpaceModel + feature: SapiSpatialFeatureModel + parcellation: SapiParcellationModel + region: SapiRegionModel + + handleOnFocus(cpt: unknown){} + handleOnDeFocus(cpt: unknown) {} +} + +export default { + component: EntryWrappercls, + decorators: [ + moduleMetadata({ + imports: [ + SxplrSapiViewsFeaturesIeegModule, + ], + providers: [ + SAPI, + ...provideDarkTheme, + ], + declarations: [ + EntryWrappercls + ] + }) + ], +} as Meta + +const Template: Story<EntryWrappercls> = (args: EntryWrappercls, { loaded }) => { + const { atlas, parc, space, region, feature } = loaded + return ({ + props: { + ...args, + atlas: atlas, + parcellation: parc, + template: space, + region: region, + feature, + handleOnFocus: action('handleOnFocus'), + handleOnDeFocus: action('handleOnDeFocus') + }, + }) +} + +const loadFeat = async () => { + const atlas = await getHumanAtlas() + const space = await getMni152() + const parc = await getJba29() + const region = await getHoc1Right() + + const features = await getMni152SpatialFeatureHoc1Right() + const spatialFeats = features.filter(f => f["@type"] === "siibra/features/ieegSession") + const feature = cleanIeegSessionDatasets(spatialFeats)[0] + return { + atlas, + space, + parc, + region, + feature + } +} + +export const Default = Template.bind({}) +Default.args = { + +} +Default.loaders = [ + async () => { + const { + atlas, + space, + feature, + parc, + region, + } = await loadFeat() + return { + atlas, space, feature, parc, region + } + } +] diff --git a/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.style.css b/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.style.css new file mode 100644 index 0000000000000000000000000000000000000000..1c554eef70e8adae01cba4530c534a389ce6c93a --- /dev/null +++ b/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.style.css @@ -0,0 +1,4 @@ +mat-form-field +{ + width: 100%; +} diff --git a/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.template.html b/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.template.html new file mode 100644 index 0000000000000000000000000000000000000000..af7464f5e24b254d42985ef55766f755696c4909 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/ieeg/ieegDataset/ieegDataset.template.html @@ -0,0 +1,112 @@ +<spinner-cmp *ngIf="busy$ | async; else resultTmpl"> +</spinner-cmp> + +<ng-template #resultTmpl> + + <mat-accordion *ngIf="!!detailedFeature"> + <ng-template + ngFor + [ngForOf]="detailedFeature.sessions | keyvalue" + let-sessionKeyVal> + + <ng-template + [ngIf]="sessionKeyVal.value.inRoi" + [ngTemplateOutlet]="sessionTmpl" + [ngTemplateOutletContext]="{ + $implicit: sessionKeyVal.value + }"> + + </ng-template> + + </ng-template> + </mat-accordion> + +</ng-template> + +<!-- session template --> +<ng-template #sessionTmpl let-session> + <mat-expansion-panel + (opened)="onPanelOpen(session)" + (closed)="onPanelClose(session)"> + <mat-expansion-panel-header> + SessionID: {{ session.sub_id }} + </mat-expansion-panel-header> + + <ng-template matExpansionPanelContent> + <ng-template + ngFor + [ngForOf]="session.electrodes | keyvalue | inRoi" + let-electrodeKeyVal> + <ng-template + [ngTemplateOutlet]="electrodeTmpl" + [ngTemplateOutletContext]="{ + electrode: electrodeKeyVal.value, + session: session + }"> + + </ng-template> + </ng-template> + + <mat-divider></mat-divider> + <ng-template + ngFor + [ngForOf]="session.electrodes | keyvalue | inRoi : false" + let-electrodeKeyVal> + <div class="sxplr-very-muted"> + <ng-template + [ngTemplateOutlet]="electrodeTmpl" + [ngTemplateOutletContext]="{ + electrode: electrodeKeyVal.value, + session: session + }"> + + </ng-template> + </div> + </ng-template> + </ng-template> + </mat-expansion-panel> +</ng-template> + +<!-- electrode template --> +<ng-template + #electrodeTmpl + let-electrode="electrode" + let-session="session"> + <mat-form-field appearance="fill"> + <mat-label> + ElectrodeID: {{ electrode.electrode_id }} + </mat-label> + <mat-chip-list> + <ng-template + ngFor + [ngForOf]="electrode.contact_points | keyvalue" + let-contactPointKeyVal> + + <ng-template + [ngTemplateOutlet]="contactPointTmpl" + [ngTemplateOutletContext]="{ + contactPointKey: contactPointKeyVal.key, + contactPoint: contactPointKeyVal.value, + electrode: electrode, + session: session + }"> + </ng-template> + </ng-template> + </mat-chip-list> + </mat-form-field> +</ng-template> + +<!-- contact point template --> +<ng-template + #contactPointTmpl + let-contactPoint="contactPoint" + let-key="contactPointKey" + let-electrode="electrode" + let-session="session"> + <mat-chip + (click)="onContactPointClicked(contactPoint, electrode, session)" + [color]="contactPoint.inRoi ? 'primary' : 'default'" + selected> + {{ key }} + </mat-chip> +</ng-template> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/ieeg/inRoi.pipe.ts b/src/atlasComponents/sapiViews/features/ieeg/inRoi.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..4f215c8ccd96ff7af503abc521e3c6558383c234 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/ieeg/inRoi.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from "@angular/core"; + +interface InRoi { + key: string + value: { + inRoi: boolean + } +} + +@Pipe({ + name: 'inRoi', + pure: true +}) + +export class InRoiPipe implements PipeTransform{ + public transform(list: InRoi[], inroi=true): InRoi[] { + return list.filter(it => it.value.inRoi === inroi) + } +} diff --git a/src/atlasComponents/sapiViews/features/ieeg/index.ts b/src/atlasComponents/sapiViews/features/ieeg/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..757808e777ca4f96b83bba521ab6c71d3903cfaa --- /dev/null +++ b/src/atlasComponents/sapiViews/features/ieeg/index.ts @@ -0,0 +1,2 @@ +export { IEEGDatasetCmp, IeegOnFocusEvent, IeegOnDefocusEvent, ContactPoint, Electrode, Session } from "./ieegDataset/ieegDataset.component" +export { SxplrSapiViewsFeaturesIeegModule } from "./module" \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/ieeg/module.ts b/src/atlasComponents/sapiViews/features/ieeg/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..c91161eac8e261121aaa58e583265961b324c007 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/ieeg/module.ts @@ -0,0 +1,33 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { MatChipsModule } from "@angular/material/chips"; +import { MatDividerModule } from "@angular/material/divider"; +import { MatExpansionModule } from "@angular/material/expansion"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { SAPIModule } from "src/atlasComponents/sapi/module"; +import { SpinnerModule } from "src/components/spinner"; +import { IEEGDatasetCmp } from "./ieegDataset/ieegDataset.component"; +import { InRoiPipe } from "./inRoi.pipe"; + +@NgModule({ + imports: [ + CommonModule, + MatExpansionModule, + MatChipsModule, + MatFormFieldModule, + MatDividerModule, + BrowserAnimationsModule, + SpinnerModule, + SAPIModule, + ], + declarations: [ + IEEGDatasetCmp, + InRoiPipe, + ], + exports: [ + IEEGDatasetCmp, + ] +}) + +export class SxplrSapiViewsFeaturesIeegModule{} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/index.ts b/src/atlasComponents/sapiViews/features/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..89e1fafbad8108576c708d401326b2d5f4137f6d --- /dev/null +++ b/src/atlasComponents/sapiViews/features/index.ts @@ -0,0 +1,7 @@ +export { + SapiViewsFeaturesModule +} from "./module" + +export { + SapiViewsFeaturesVoiQuery +} from "./voi" diff --git a/src/atlasComponents/sapiViews/features/module.ts b/src/atlasComponents/sapiViews/features/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..3df348c72f79be4b53c3050e30627c896e8b93f2 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/module.ts @@ -0,0 +1,56 @@ +import { CommonModule, DOCUMENT } from "@angular/common" +import { NgModule } from "@angular/core" +import { AngularMaterialModule } from "src/sharedModules" +import { appendScriptFactory, APPEND_SCRIPT_TOKEN } from "src/util/constants" +import { FeatureEntryCmp } from "./entry/entry.component" +import { SapiViewsFeaturesEntryListItem } from "./entryListItem/entryListItem.component" +import { FeatureBadgeColourPipe } from "./featureBadgeColor.pipe" +import { FeatureBadgeFlagPipe } from "./featureBadgeFlag.pipe" +import { FeatureBadgeNamePipe } from "./featureBadgeName.pipe" +import * as ieeg from "./ieeg" +import * as receptor from "./receptors" +import {SapiViewsFeatureConnectivityModule} from "src/atlasComponents/sapiViews/features/connectivity"; +import * as voi from "./voi" +import { OrderFilterFeaturesPipe } from "./orderFilterFeatureList.pipe" + +const { + SxplrSapiViewsFeaturesIeegModule +} = ieeg +const { + ReceptorViewModule +} = receptor +const { SapiViewsFeaturesVoiModule } = voi + +@NgModule({ + imports: [ + CommonModule, + ReceptorViewModule, + SxplrSapiViewsFeaturesIeegModule, + AngularMaterialModule, + SapiViewsFeaturesVoiModule, + SapiViewsFeatureConnectivityModule, + ], + declarations: [ + FeatureEntryCmp, + FeatureBadgeNamePipe, + FeatureBadgeColourPipe, + FeatureBadgeFlagPipe, + SapiViewsFeaturesEntryListItem, + OrderFilterFeaturesPipe, + ], + providers: [ + { + provide: APPEND_SCRIPT_TOKEN, + useFactory: appendScriptFactory, + deps: [ DOCUMENT ] + } + ], + exports: [ + FeatureEntryCmp, + SapiViewsFeaturesEntryListItem, + SapiViewsFeaturesVoiModule, + SapiViewsFeatureConnectivityModule, + OrderFilterFeaturesPipe, + ] +}) +export class SapiViewsFeaturesModule{} diff --git a/src/atlasComponents/sapiViews/features/orderFilterFeatureList.pipe.ts b/src/atlasComponents/sapiViews/features/orderFilterFeatureList.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..382862b0300f4bdaffadce20aeb4e28274a8bef6 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/orderFilterFeatureList.pipe.ts @@ -0,0 +1,37 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { CLEANED_IEEG_DATASET_TYPE, SapiFeatureModel, SxplrCleanedFeatureModel } from "src/atlasComponents/sapi/type"; +import { environment } from "src/environments/environment" + +type PipableFeatureType = SapiFeatureModel | SxplrCleanedFeatureModel + +type ArrayOperation<T extends boolean | number> = (input: PipableFeatureType) => T + +const FILTER_FN: ArrayOperation<boolean> = feature => { + return feature["@type"] !== "siibra/features/cells" +} + +const ORDER_LIST: ArrayOperation<number> = feature => { + if (feature["@type"] === "siibra/features/receptor") return -4 + if (feature["@type"] === CLEANED_IEEG_DATASET_TYPE) return -3 + if (feature['@type'] === "https://openminds.ebrains.eu/core/DatasetVersion") return 2 + return 0 +} + +@Pipe({ + name: 'orderFilterFeatures', + pure: true +}) + +export class OrderFilterFeaturesPipe implements PipeTransform{ + public transform(inputFeatures: PipableFeatureType[]): PipableFeatureType[] { + return inputFeatures + .filter(f => { + /** + * if experimental flag is set, do not filter out anything + */ + if (environment.EXPERIMENTAL_FEATURE_FLAG) return true + return FILTER_FN(f) + }) + .sort((a, b) => ORDER_LIST(a) - ORDER_LIST(b)) + } +} diff --git a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..093ec6a110eaca060b0bf3e48204026ee5ed1946 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts @@ -0,0 +1,146 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Component, ViewChild } from "@angular/core" +import { FormsModule } from "@angular/forms" +import { BrowserAnimationsModule } from "@angular/platform-browser/animations" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" +import { getHoc1RightFeatureDetail, getHoc1RightFeatures, getHoc1Right, getHumanAtlas, getJba29, getMni152 } from "src/atlasComponents/sapi/stories.base" +import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type" +import { AngularMaterialModule } from "src/sharedModules" +import { Autoradiography } from "./autoradiography.component" + +@Component({ + selector: 'autoradiograph-wrapper-cmp', + template: ` + <mat-form-field appearance="fill"> + <mat-select [(ngModel)]="selectedSymbol"> + <mat-option value="" disabled> + --select-- + </mat-option> + + <mat-option [value]="option" + *ngFor="let option of options"> + {{ option }} + </mat-option> + </mat-select> + </mat-form-field> + <sxplr-sapiviews-features-receptor-autoradiograph + class="d-inline-block w-100 h-100" + [sxplr-sapiviews-features-receptor-atlas]="atlas" + [sxplr-sapiviews-features-receptor-parcellation]="parcellation" + [sxplr-sapiviews-features-receptor-template]="template" + [sxplr-sapiviews-features-receptor-region]="region" + [sxplr-sapiviews-features-receptor-featureid]="featureId" + [sxplr-sapiviews-features-receptor-data]="feature" + [sxplr-sapiviews-features-receptor-autoradiograph-selected-symbol]="selectedSymbol" + > + </sxplr-sapiviews-features-receptor-autoradiograph> + `, + styles: [ + ` + :host + { + display: block; + max-width: 24rem; + max-height: 24rem; + } + ` + ] +}) +class AutoRadiographWrapperCls { + atlas: SapiAtlasModel + parcellation: SapiParcellationModel + template: SapiSpaceModel + region: SapiRegionModel + + feature: SapiRegionalFeatureReceptorModel + featureId: string + + @ViewChild(Autoradiography) + ar: Autoradiography + + selectedSymbol: string + + get options(){ + return Object.keys(this.feature?.data?.autoradiographs || this.ar?.receptorData?.data?.autoradiographs || {}) + } +} + +export default { + component: AutoRadiographWrapperCls, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + AngularMaterialModule, + HttpClientModule, + BrowserAnimationsModule, + FormsModule, + ], + providers: [ + SAPI + ], + declarations: [ + Autoradiography + ] + }) + ], +} as Meta + +const Template: Story<AutoRadiographWrapperCls> = (args: AutoRadiographWrapperCls, { loaded }) => { + const { atlas, parc, space, region, featureId, feature } = loaded + return ({ + props: { + ...args, + atlas: atlas, + parcellation: parc, + template: space, + region: region, + feature, + featureId, + }, + }) +} + +const loadFeat = async () => { + const atlas = await getHumanAtlas() + const parc = await getJba29() + const region = await getHoc1Right() + const space = await getMni152() + const features = await getHoc1RightFeatures() + const receptorfeat = features.find(f => f['@type'] === "siibra/features/receptor") + const feature = await getHoc1RightFeatureDetail(receptorfeat["@id"]) + return { + atlas, + parc, + space, + region, + featureId: receptorfeat["@id"], + feature + } +} + +export const Default = Template.bind({}) +Default.args = { + +} +Default.loaders = [ + async () => { + const { atlas, parc, space, region, featureId } = await loadFeat() + return { + atlas, parc, space, region, featureId + } + } +] + +export const LoadViaDirectlyInjectData = Template.bind({}) +LoadViaDirectlyInjectData.loaders = [ + async () => { + + const { feature } = await loadFeat() + return { + feature + } + } +] \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..ecd059f0a8fd1411e9a5c7b211c7aac78cff91ae --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts @@ -0,0 +1,82 @@ +import { AfterViewInit, Component, ElementRef, Input, OnChanges, SimpleChanges } from "@angular/core"; +import { SAPI } from "src/atlasComponents/sapi"; +import { PARSE_TYPEDARRAY } from "src/atlasComponents/sapi/sapi.service"; +import { BaseReceptor } from "../base"; + +@Component({ + selector: `sxplr-sapiviews-features-receptor-autoradiograph`, + templateUrl: './autoradiography.template.html', + styleUrls: [ + './autoradiography.style.css' + ], + exportAs: 'sxplrSapiViewsFeaturesReceptorAR' +}) + +export class Autoradiography extends BaseReceptor implements OnChanges, AfterViewInit{ + + @Input('sxplr-sapiviews-features-receptor-autoradiograph-selected-symbol') + selectedSymbol: string + + private pleaseRender = false + + width: number + height: number + renderBuffer: Uint8ClampedArray + + async ngOnChanges(simpleChanges: SimpleChanges) { + await super.ngOnChanges(simpleChanges) + if (!this.receptorData) { + return + } + if (this.selectedSymbol) { + const fp = this.receptorData.data.autoradiographs[this.selectedSymbol] + if (!fp) { + this.error = `selectedSymbol ${this.selectedSymbol} cannot be found. Valid symbols are ${Object.keys(this.receptorData.data.autoradiographs)}` + return + } + const { "x-width": width, "x-height": height } = fp + + this.width = width + this.height = height + + const { result } = await this.sapi.processNpArrayData<PARSE_TYPEDARRAY.CANVAS_FORTRAN_RGBA>(fp, PARSE_TYPEDARRAY.CANVAS_FORTRAN_RGBA) + this.renderBuffer = result + this.rerender() + } + } + constructor(sapi: SAPI, private el: ElementRef){ + super(sapi) + } + + ngAfterViewInit(): void { + if (this.pleaseRender) this.rerender() + } + + rerender(){ + this.dataBlobAvailable = false + if (!this.el || !this.renderBuffer) { + this.pleaseRender = true + return + } + + const arContainer = (this.el.nativeElement as HTMLElement) + while (arContainer.firstChild) { + arContainer.removeChild(arContainer.firstChild) + } + + const canvas = document.createElement("canvas") + canvas.height = this.height + canvas.width = this.width + arContainer.appendChild(canvas) + const ctx = canvas.getContext("2d") + const imgData = ctx.createImageData(this.width, this.height) + imgData.data.set(this.renderBuffer) + ctx.putImageData(imgData, 0, 0) + + canvas.toBlob(blob => { + this.dataBlobAvailable = true + this.dataBlob$.next(blob) + }) + this.pleaseRender = false + } +} diff --git a/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.style.css b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.style.css similarity index 100% rename from src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.style.css rename to src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.style.css diff --git a/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.style.css b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.template.html similarity index 100% rename from src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.style.css rename to src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.template.html diff --git a/src/atlasComponents/sapiViews/features/receptors/base.ts b/src/atlasComponents/sapiViews/features/receptors/base.ts new file mode 100644 index 0000000000000000000000000000000000000000..2428c72f6e315c97f98a5914c867d7c6c7560af8 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/base.ts @@ -0,0 +1,100 @@ +import { Directive, Input, SimpleChanges } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; +import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type"; + +@Directive() +export abstract class BaseReceptor{ + + @Input('sxplr-sapiviews-features-receptor-atlas') + atlas: SapiAtlasModel + + @Input('sxplr-sapiviews-features-receptor-parcellation') + parcellation: SapiParcellationModel + + @Input('sxplr-sapiviews-features-receptor-template') + template: SapiSpaceModel + + @Input('sxplr-sapiviews-features-receptor-region') + region: SapiRegionModel + + @Input('sxplr-sapiviews-features-receptor-featureid') + featureId: string + + @Input('sxplr-sapiviews-features-receptor-data') + receptorData: SapiRegionalFeatureReceptorModel + + error: string + + async ngOnChanges(simpleChanges: SimpleChanges) { + if (simpleChanges.receptorData?.currentValue) { + this.rerender() + return + } + if (this.canFetch) { + this.receptorData = await this.fetchReceptorData() + this.rerender() + } + } + + private get canFetch() { + if (!this.atlas) { + this.error = `atlas needs to be defined, but is not` + return false + } + if (!this.parcellation) { + this.error = `parcellation needs to be defined, but is not` + return false + } + if (!this.region) { + this.error = `region needs to be defined, but is not` + return false + } + if (!this.featureId) { + this.error = `featureId needs to be defined, but is not` + return false + } + return true + } + + protected async fetchReceptorData() { + this.error = null + if (!this.atlas) { + this.error = `atlas needs to be defined, but is not` + return + } + if (!this.parcellation) { + this.error = `parcellation needs to be defined, but is not` + return + } + if (!this.region) { + this.error = `region needs to be defined, but is not` + return + } + if (!this.featureId) { + this.error = `featureId needs to be defined, but is not` + return + } + const result = await this.sapi.getRegion(this.atlas["@id"], this.parcellation["@id"], this.region.name).getFeatureInstance(this.featureId, this.template["@id"]).toPromise() + if (result["@type"] !== "siibra/features/receptor") { + throw new Error(`BaseReceptor Error. Expected .type to be "siibra/features/receptor", but was "${result['@type']}"`) + } + return result + } + + abstract rerender(): void + + /** + * flag to indicate that getDataBlob() can be called. + */ + dataBlobAvailable = false + /** + * blob object observable, representing the data of the component. This allows the data to be downloaded. + */ + dataBlob$ = new BehaviorSubject<Blob>(null) + + constructor( + protected sapi: SAPI + ){ + } +} diff --git a/src/atlasComponents/sapiViews/features/receptors/entry/entry.component.ts b/src/atlasComponents/sapiViews/features/receptors/entry/entry.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f6a756c1b26a15d26a93c4900347072cd79be27 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/entry/entry.component.ts @@ -0,0 +1,43 @@ +import { ChangeDetectorRef, Component, OnChanges, SimpleChanges } from "@angular/core"; +import { SAPI } from "src/atlasComponents/sapi"; +import { BaseReceptor } from "../base"; + +@Component({ + selector: 'sxplr-sapiviews-features-receptor-entry', + templateUrl: `./entry.template.html`, + styleUrls: [ + `./entry.style.css` + ] +}) + +export class Entry extends BaseReceptor implements OnChanges { + selectedSymbol: string + symbolsOptions: string[] = [] + + async ngOnChanges(simpleChanges: SimpleChanges): Promise<void> { + await super.ngOnChanges(simpleChanges) + } + + loading = true + rerender(): void { + if (this.receptorData.data.receptor_symbols) { + this.loading = false + this.symbolsOptions = Object.keys(this.receptorData.data.receptor_symbols) + } + this.cdr.detectChanges() + } + constructor( + sapi: SAPI, + private cdr: ChangeDetectorRef, + ){ + super(sapi) + } + + setSelectedSymbol(select: string){ + this.selectedSymbol = select + } + + getDataBlob(): Promise<Blob> { + throw new Error(`cannot get blob of entry component`) + } +} diff --git a/src/atlasComponents/sapiViews/features/receptors/entry/entry.stories.ts b/src/atlasComponents/sapiViews/features/receptors/entry/entry.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..63f07ee1ef4f6dd57c402e07d08a4e9c5d742f01 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/entry/entry.stories.ts @@ -0,0 +1,127 @@ +import { CommonModule, DOCUMENT } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" +import { getHoc1RightFeatureDetail, getHoc1RightFeatures, getHoc1Right, getHumanAtlas, getJba29, getMni152, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { Entry } from "./entry.component" +import { ReceptorViewModule } from ".." +import { appendScriptFactory, APPEND_SCRIPT_TOKEN } from "src/util/constants" +import { Component, Output } from "@angular/core" +import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type" + +@Component({ + selector: 'entry-wrapper-cmp', + template: ` + <sxplr-sapiviews-features-receptor-entry + [sxplr-sapiviews-features-receptor-atlas]="atlas" + [sxplr-sapiviews-features-receptor-parcellation]="parcellation" + [sxplr-sapiviews-features-receptor-template]="template" + [sxplr-sapiviews-features-receptor-region]="region" + [sxplr-sapiviews-features-receptor-featureid]="featureId" + [sxplr-sapiviews-features-receptor-data]="feature" + > + </sxplr-sapiviews-features-receptor-entry> + `, + styles: [ + ` + :host + { + display: block; + width: 20rem; + } + ` + ] +}) +class EntryWrappercls { + atlas: SapiAtlasModel + parcellation: SapiParcellationModel + template: SapiSpaceModel + region: SapiRegionModel + feature: SapiRegionalFeatureReceptorModel + featureId: string + +} + +export default { + component: EntryWrappercls, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + ReceptorViewModule, + AngularMaterialModule, + ], + providers: [ + SAPI, + { + provide: APPEND_SCRIPT_TOKEN, + useFactory: appendScriptFactory, + deps: [ DOCUMENT ] + }, + ...provideDarkTheme, + ], + declarations: [ + EntryWrappercls + ] + }) + ], +} as Meta + +const Template: Story<Entry> = (args: Entry, { loaded }) => { + const { atlas, parc, space, region, featureId, feature } = loaded + return ({ + props: { + ...args, + atlas: atlas, + parcellation: parc, + template: space, + region: region, + feature, + featureId, + }, + }) +} + +const loadFeat = async () => { + const atlas = await getHumanAtlas() + const parc = await getJba29() + const region = await getHoc1Right() + const space = await getMni152() + const features = await getHoc1RightFeatures() + const receptorfeat = features.find(f => f['@type'] === "siibra/features/receptor") + const feature = await getHoc1RightFeatureDetail(receptorfeat["@id"]) + return { + atlas, + parc, + space, + region, + featureId: receptorfeat["@id"], + feature + } +} + +export const Default = Template.bind({}) +Default.args = { + +} +Default.loaders = [ + async () => { + const { atlas, parc, space, region, featureId } = await loadFeat() + return { + atlas, parc, space, region, featureId + } + } +] + +export const LoadViaDirectlyInjectData = Template.bind({}) +LoadViaDirectlyInjectData.loaders = [ + async () => { + + const { feature } = await loadFeat() + return { + feature + } + } +] \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/receptors/entry/entry.style.css b/src/atlasComponents/sapiViews/features/receptors/entry/entry.style.css new file mode 100644 index 0000000000000000000000000000000000000000..0645dc01a568740a2c3d2f51c486adcb7f111a02 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/entry/entry.style.css @@ -0,0 +1,4 @@ +:host +{ + display: block; +} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/receptors/entry/entry.template.html b/src/atlasComponents/sapiViews/features/receptors/entry/entry.template.html new file mode 100644 index 0000000000000000000000000000000000000000..e88ce49a8da4549d21f77e950af2c9a1361a4fb1 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/entry/entry.template.html @@ -0,0 +1,80 @@ +<mat-card> + <spinner-cmp *ngIf="loading"></spinner-cmp> + + <ng-template [ngTemplateOutlet]="downloadBtn" + [ngTemplateOutletContext]="{ + label: 'fingerprint.tsv', + filename: 'fingerprint.tsv', + receptorCmp: fp + }"> + </ng-template> + <sxplr-sapiviews-features-receptor-fingerprint + [sxplr-sapiviews-features-receptor-data]="receptorData" + (sxplr-sapiviews-features-receptor-fingerprint-receptor-selected)="setSelectedSymbol($event)" + #fp="sxplrSapiViewsFeaturesReceptorFP" + > + </sxplr-sapiviews-features-receptor-fingerprint> + + <mat-form-field appearance="fill" class="w-100"> + <mat-select [(ngModel)]="selectedSymbol"> + <mat-option value="null" disabled> + --select-- + </mat-option> + + <mat-option [value]="option" + *ngFor="let option of symbolsOptions"> + {{ option }} + </mat-option> + </mat-select> + </mat-form-field> + + <ng-template [ngIf]="selectedSymbol"> + + <ng-template [ngTemplateOutlet]="downloadBtn" + [ngTemplateOutletContext]="{ + label: 'profile.tsv', + filename: 'profile.tsv', + receptorCmp: profile + }"> + </ng-template> + <sxplr-sapiviews-features-receptor-profile + [sxplr-sapiviews-features-receptor-data]="receptorData" + [sxplr-sapiviews-features-receptor-profile-selected-symbol]="selectedSymbol" + #profile="sxplrSapiViewsFeaturesReceptorProfile"> + </sxplr-sapiviews-features-receptor-profile> + + + <ng-template [ngTemplateOutlet]="downloadBtn" + [ngTemplateOutletContext]="{ + label: 'autoradiograph.png', + filename: 'autoradiograph.png', + receptorCmp: ar + }"> + </ng-template> + <sxplr-sapiviews-features-receptor-autoradiograph + [sxplr-sapiviews-features-receptor-data]="receptorData" + [sxplr-sapiviews-features-receptor-autoradiograph-selected-symbol]="selectedSymbol" + #ar="sxplrSapiViewsFeaturesReceptorAR" + > + </sxplr-sapiviews-features-receptor-autoradiograph> + </ng-template> + +</mat-card> + + +<!-- download data button template --> +<ng-template #downloadBtn + let-label="label" + let-filename="filename" + let-receptorCmp="receptorCmp"> + <button mat-button + *ngIf="receptorCmp.dataBlobAvailable" + single-file-output + [single-file-output-blob]="receptorCmp.dataBlob$ | async" + [single-file-output-filename]="filename"> + <i class="fas fa-download"></i> + <span> + {{ label }} + </span> + </button> +</ng-template> diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..98c5b0076da0350cd0a30fdc01633ee57b6e3bab --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts @@ -0,0 +1,128 @@ +import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, Inject, OnChanges, OnDestroy, Output, SimpleChanges, ViewChild } from "@angular/core"; +import { fromEvent, Observable, Subscription } from "rxjs"; +import { distinctUntilChanged, map } from "rxjs/operators"; +import { SAPI } from "src/atlasComponents/sapi"; +import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type"; +import { DARKTHEME } from "src/util/injectionTokens"; +import { BaseReceptor } from "../base"; + +/** + * kg-dataset-dumb-radar requires input to be a different shape + * once the the return signature + */ +type RequiredType = { + receptor: { + label: string + } + density: { + mean: number + sd: number + unit: string + } +} + +function transformRadar(input: SapiRegionalFeatureReceptorModel['data']['fingerprints']): RequiredType[]{ + const listRequired: RequiredType[] = [] + for (const key in input) { + const item = input[key] + listRequired.push({ + receptor: { + label: key + }, + density: { + mean: item.mean, + sd: item.std, + unit: item.unit + } + }) + + } + return listRequired +} + +@Component({ + selector: 'sxplr-sapiviews-features-receptor-fingerprint', + templateUrl: './fingerprint.template.html', + styleUrls: [ + './fingerprint.style.css' + ], + exportAs: "sxplrSapiViewsFeaturesReceptorFP" +}) + +export class Fingerprint extends BaseReceptor implements OnChanges, AfterViewInit, OnDestroy{ + + @Output('sxplr-sapiviews-features-receptor-fingerprint-receptor-selected') + selectReceptor = new EventEmitter<string>() + + @HostListener('click') + onClick(){ + if (this.mouseOverReceptor) { + this.selectReceptor.emit(this.mouseOverReceptor) + } + } + + async ngOnChanges(simpleChanges: SimpleChanges) { + await super.ngOnChanges(simpleChanges) + } + + constructor(sapi: SAPI, private el: ElementRef, @Inject(DARKTHEME) public darktheme$: Observable<boolean>){ + super(sapi) + } + + get dumbRadarCmp(){ + return this.el?.nativeElement?.querySelector('kg-dataset-dumb-radar') + } + + private setDumbRadarPlease = false + private sub: Subscription[] = [] + private mouseOverReceptor: string + + ngOnDestroy(){ + while (this.sub.length > 0) this.sub.pop().unsubscribe() + } + + ngAfterViewInit(){ + if (this.setDumbRadarPlease) { + this.rerender() + } + + this.sub.push( + fromEvent<CustomEvent>(this.el.nativeElement, 'kg-ds-prv-regional-feature-mouseover').pipe( + map(ev => ev.detail?.data?.receptor?.label), + distinctUntilChanged(), + ).subscribe(label => { + this.mouseOverReceptor = label + }) + ) + } + + rerender(): void { + + if (!this.dumbRadarCmp) { + this.setDumbRadarPlease = true + return + } + + this.dumbRadarCmp.metaBs = this.receptorData.data.receptor_symbols + this.dumbRadarCmp.radar= transformRadar(this.receptorData.data.fingerprints) + + this.dataBlob$.next(this.getDataBlob()) + this.dataBlobAvailable = true + this.setDumbRadarPlease = false + } + + private getDataBlob(): Blob { + if (!this.receptorData?.data?.fingerprints) throw new Error(`raw data unavailable. Try again later.`) + const fingerprints = this.receptorData.data.fingerprints + const output: string[] = [] + output.push( + ["name", "mean", "std"].join("\t") + ) + for (const key in fingerprints) { + output.push( + [key, fingerprints[key].mean, fingerprints[key].std].join("\t") + ) + } + return new Blob([output.join("\n")], { type: 'text/tab-separated-values' }) + } +} diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c3327ac1470cc2a7d650aa3b5196f70958dbbe2 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts @@ -0,0 +1,128 @@ +import { CommonModule, DOCUMENT } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Component, EventEmitter, Output } from "@angular/core" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" +import { getHoc1RightFeatureDetail, getHoc1RightFeatures, getHoc1Right, getHumanAtlas, getJba29, getMni152, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type" +import { AngularMaterialModule } from "src/sharedModules" +import { appendScriptFactory, APPEND_SCRIPT_TOKEN } from "src/util/constants" +import { ReceptorViewModule } from ".." + +@Component({ + selector: 'fingerprint-wrapper-cmp', + template: ` + <sxplr-sapiviews-features-receptor-fingerprint + [sxplr-sapiviews-features-receptor-atlas]="atlas" + [sxplr-sapiviews-features-receptor-parcellation]="parcellation" + [sxplr-sapiviews-features-receptor-template]="template" + [sxplr-sapiviews-features-receptor-region]="region" + [sxplr-sapiviews-features-receptor-featureid]="featureId" + [sxplr-sapiviews-features-receptor-data]="feature" + (sxplr-sapiviews-features-receptor-fingerprint-receptor-selected)="selectReceptor.emit($event)" + > + </sxplr-sapiviews-features-receptor-fingerprint> + `, + styles: [ + ` + :host + { + display: block; + width: 20rem; + height: 20rem; + } + ` + ] +}) +class FingerprintWrapperCls { + atlas: SapiAtlasModel + parcellation: SapiParcellationModel + template: SapiSpaceModel + region: SapiRegionModel + feature: SapiRegionalFeatureReceptorModel + featureId: string + + @Output() + selectReceptor = new EventEmitter() +} + +export default { + component: FingerprintWrapperCls, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + AngularMaterialModule, + HttpClientModule, + ReceptorViewModule, + ], + providers: [ + SAPI, + { + provide: APPEND_SCRIPT_TOKEN, + useFactory: appendScriptFactory, + deps: [ DOCUMENT ] + }, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<FingerprintWrapperCls> = (args: FingerprintWrapperCls, { loaded }) => { + const { atlas, parc, space, region, featureId, feature } = loaded + return ({ + props: { + ...args, + atlas: atlas, + parcellation: parc, + template: space, + region: region, + feature, + featureId, + }, + }) +} + +const loadFeat = async () => { + const atlas = await getHumanAtlas() + const parc = await getJba29() + const region = await getHoc1Right() + const space = await getMni152() + const features = await getHoc1RightFeatures() + const receptorfeat = features.find(f => f['@type'] === "siibra/features/receptor") + const feature = await getHoc1RightFeatureDetail(receptorfeat["@id"]) + return { + atlas, + parc, + space, + region, + featureId: receptorfeat["@id"], + feature + } +} + +export const Default = Template.bind({}) +Default.args = { + +} +Default.loaders = [ + async () => { + const { atlas, parc, space, region, featureId } = await loadFeat() + return { + atlas, parc, space, region, featureId + } + } +] + +export const LoadViaDirectlyInjectData = Template.bind({}) +LoadViaDirectlyInjectData.loaders = [ + async () => { + + const { feature } = await loadFeat() + return { + feature + } + } +] diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.style.css b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.style.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.template.html b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.template.html new file mode 100644 index 0000000000000000000000000000000000000000..c315de7303ead0ebb5e0c2032fd4a647d381dd39 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.template.html @@ -0,0 +1,3 @@ +<kg-dataset-dumb-radar + [attr.kg-ds-prv-darkmode]="darktheme$ | async"> +</kg-dataset-dumb-radar> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/receptors/index.ts b/src/atlasComponents/sapiViews/features/receptors/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..7ccaf9d94dbb4e164a98d600c701de5f419ed346 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/index.ts @@ -0,0 +1,4 @@ +export { Autoradiography } from "./autoradiography/autoradiography.component"; +export { Fingerprint } from "./fingerprint/fingerprint.component" +export { Profile } from "./profile/profile.component" +export { ReceptorViewModule } from "./module" diff --git a/src/atlasComponents/sapiViews/features/receptors/module.ts b/src/atlasComponents/sapiViews/features/receptors/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..d5bd04dc6b11f71c1646b4c8b2892c6ddf372f6b --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/module.ts @@ -0,0 +1,55 @@ +import { CommonModule } from "@angular/common"; +import { APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { SpinnerModule } from "src/components/spinner"; +import { AngularMaterialModule } from "src/sharedModules"; +import { APPEND_SCRIPT_TOKEN } from "src/util/constants"; +import { ZipFilesOutputModule } from "src/zipFilesOutput/module"; +import { Autoradiography } from "./autoradiography/autoradiography.component"; +import { Entry } from "./entry/entry.component"; +import { Fingerprint } from "./fingerprint/fingerprint.component" +import { Profile } from "./profile/profile.component" + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule, + FormsModule, + BrowserAnimationsModule, + SpinnerModule, + ZipFilesOutputModule, + ], + declarations: [ + Autoradiography, + Fingerprint, + Profile, + Entry, + ], + exports: [ + Autoradiography, + Fingerprint, + Profile, + Entry, + ], + providers: [{ + provide: APP_INITIALIZER, + multi: true, + useFactory: (appendScriptFn: (url: string) => Promise<any>) => { + + const libraries = [ + 'https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js', + 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.1.2/es5/tex-svg.js' + ] + return () => Promise.all(libraries.map(appendScriptFn)) + }, + deps: [ + APPEND_SCRIPT_TOKEN + ] + }], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA, + ] +}) + +export class ReceptorViewModule{} diff --git a/src/atlasComponents/sapiViews/features/receptors/profile/profile.component.ts b/src/atlasComponents/sapiViews/features/receptors/profile/profile.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..864584597568df263ed93be923f757bcd585f125 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/profile/profile.component.ts @@ -0,0 +1,91 @@ +import { AfterViewInit, Component, ElementRef, Inject, Input, OnChanges, SimpleChanges } from "@angular/core"; +import { Observable } from "rxjs"; +import { SAPI } from "src/atlasComponents/sapi"; +import { PARSE_TYPEDARRAY } from "src/atlasComponents/sapi/sapi.service"; +import { DARKTHEME } from "src/util/injectionTokens"; +import { BaseReceptor } from "../base"; + +@Component({ + selector: `sxplr-sapiviews-features-receptor-profile`, + templateUrl: './profile.template.html', + styleUrls: [ + './profile.style.css' + ], + exportAs: "sxplrSapiViewsFeaturesReceptorProfile" +}) + +export class Profile extends BaseReceptor implements AfterViewInit, OnChanges{ + + @Input('sxplr-sapiviews-features-receptor-profile-selected-symbol') + selectedSymbol: string + + private pleaseRender = false + dumbLineData: Record<number, number> + + constructor(sapi: SAPI, private el: ElementRef, @Inject(DARKTHEME) public darktheme$: Observable<boolean> ){ + super(sapi) + } + + async ngOnChanges(simpleChanges: SimpleChanges) { + await super.ngOnChanges(simpleChanges) + if (!this.receptorData) { + return + } + if (this.selectedSymbol) { + this.rerender() + } + } + + ngAfterViewInit(): void { + if (this.pleaseRender) { + this.rerender() + } + } + + get dumbLineCmp(){ + return this.el?.nativeElement?.querySelector('kg-dataset-dumb-line') + } + + async rerender() { + this.dataBlobAvailable = false + if (!this.dumbLineCmp) { + this.pleaseRender = true + return + } + this.pleaseRender = false + this.dumbLineData = null + + if (this.receptorData?.data?.profiles?.[this.selectedSymbol]) { + const { rawArray } = await this.sapi.processNpArrayData<PARSE_TYPEDARRAY.RAW_ARRAY>( + this.receptorData.data.profiles[this.selectedSymbol].density, + PARSE_TYPEDARRAY.RAW_ARRAY + ) + if (rawArray.length !== 1) { + this.error = `expected rawArray.length to be 1, but is ${rawArray.length} instead` + return + } + const prof = rawArray[0] + this.dumbLineData = {} + for (const idx in prof) { + this.dumbLineData[idx] = prof[idx] + } + this.dataBlob$.next(this.getDataBlob()) + this.dataBlobAvailable = true + this.dumbLineCmp.profileBs = this.dumbLineData + } + } + + private getDataBlob(): Blob { + if (!this.dumbLineData) throw new Error(`data has not been populated. Perhaps wait until render finishes?`) + const output: string[] = [] + output.push( + ["cortical depth (%)", "receptor density (fmol/mg)"].join("\t") + ) + for (const key in this.dumbLineData) { + output.push( + [key, this.dumbLineData[key]].join("\t") + ) + } + return new Blob([output.join("\n")], { type: 'text/tab-separated-values' }) + } +} diff --git a/src/atlasComponents/sapiViews/features/receptors/profile/profile.stories.ts b/src/atlasComponents/sapiViews/features/receptors/profile/profile.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..83d309f52d008d14183329f0539cf179c0354867 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/profile/profile.stories.ts @@ -0,0 +1,155 @@ +import { CommonModule, DOCUMENT } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Component, NgZone, ViewChild } from "@angular/core" +import { FormsModule } from "@angular/forms" +import { BrowserAnimationsModule } from "@angular/platform-browser/animations" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { BehaviorSubject, Subject } from "rxjs" +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" +import { addAddonEventListener, getHoc1RightFeatureDetail, getHoc1RightFeatures, getHoc1Right, getHumanAtlas, getJba29, getMni152, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type" +import { AngularMaterialModule } from "src/sharedModules" +import { appendScriptFactory, APPEND_SCRIPT_TOKEN } from "src/util/constants" +import { DARKTHEME } from "src/util/injectionTokens" +import { ReceptorViewModule } from ".." +import { Profile } from "./profile.component" + +@Component({ + selector: 'autoradiograph-wrapper-cmp', + template: ` + <mat-form-field appearance="fill"> + <mat-select [(ngModel)]="selectedSymbol"> + <mat-option value="" disabled> + --select-- + </mat-option> + + <mat-option [value]="option" + *ngFor="let option of options"> + {{ option }} + </mat-option> + </mat-select> + </mat-form-field> + <sxplr-sapiviews-features-receptor-profile + class="d-inline-block w-100 h-100" + [sxplr-sapiviews-features-receptor-atlas]="atlas" + [sxplr-sapiviews-features-receptor-parcellation]="parcellation" + [sxplr-sapiviews-features-receptor-template]="template" + [sxplr-sapiviews-features-receptor-region]="region" + [sxplr-sapiviews-features-receptor-featureid]="featureId" + [sxplr-sapiviews-features-receptor-data]="feature" + [sxplr-sapiviews-features-receptor-profile-selected-symbol]="selectedSymbol" + > + </sxplr-sapiviews-features-receptor-profile> + `, + styles: [ + ` + :host + { + display: block; + max-width: 24rem; + max-height: 24rem; + } + ` + ] +}) +class ProfileWrapperCls { + atlas: SapiAtlasModel + parcellation: SapiParcellationModel + template: SapiSpaceModel + region: SapiRegionModel + + feature: SapiRegionalFeatureReceptorModel + featureId: string + + @ViewChild(Profile) + profile: Profile + + selectedSymbol: string + + get options(){ + return Object.keys(this.feature?.data?.profiles || this.profile?.receptorData?.data?.profiles || {}) + } +} + +export default { + component: ProfileWrapperCls, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + AngularMaterialModule, + HttpClientModule, + BrowserAnimationsModule, + FormsModule, + ReceptorViewModule, + ], + providers: [ + SAPI, + { + provide: APPEND_SCRIPT_TOKEN, + useFactory: appendScriptFactory, + deps: [ DOCUMENT ] + }, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<ProfileWrapperCls> = (args: ProfileWrapperCls, { loaded }) => { + const { atlas, parc, space, region, featureId, feature } = loaded + return ({ + props: { + ...args, + atlas: atlas, + parcellation: parc, + template: space, + region: region, + feature, + featureId, + }, + }) +} + +const loadFeat = async () => { + const atlas = await getHumanAtlas() + const parc = await getJba29() + const region = await getHoc1Right() + const space = await getMni152() + const features = await getHoc1RightFeatures() + const receptorfeat = features.find(f => f['@type'] === "siibra/features/receptor") + const feature = await getHoc1RightFeatureDetail(receptorfeat["@id"]) + return { + atlas, + parc, + space, + region, + featureId: receptorfeat["@id"], + feature + } +} + +export const Default = Template.bind({}) +Default.args = { + +} +Default.loaders = [ + async () => { + const { atlas, parc, space, region, featureId } = await loadFeat() + return { + atlas, parc, space, region, featureId + } + } +] + +export const LoadViaDirectlyInjectData = Template.bind({}) +LoadViaDirectlyInjectData.loaders = [ + async () => { + + const { feature } = await loadFeat() + return { + feature + } + } +] diff --git a/src/atlasComponents/sapiViews/features/receptors/profile/profile.style.css b/src/atlasComponents/sapiViews/features/receptors/profile/profile.style.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/atlasComponents/sapiViews/features/receptors/profile/profile.template.html b/src/atlasComponents/sapiViews/features/receptors/profile/profile.template.html new file mode 100644 index 0000000000000000000000000000000000000000..182db6578a311b5661b8a571c927ab11d6c149ae --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/profile/profile.template.html @@ -0,0 +1,3 @@ +<kg-dataset-dumb-line + [attr.kg-ds-prv-darkmode]="darktheme$ | async"> +</kg-dataset-dumb-line> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/voi/index.ts b/src/atlasComponents/sapiViews/features/voi/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..55b2fbc01ff6805ae01ef334e88d52db40ded951 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/voi/index.ts @@ -0,0 +1,2 @@ +export { SapiViewsFeaturesVoiModule } from "./module" +export { SapiViewsFeaturesVoiQuery } from "./voiQuery.directive" diff --git a/src/atlasComponents/sapiViews/features/voi/module.ts b/src/atlasComponents/sapiViews/features/voi/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..e9817aacad60533082cbf4be70c62831d50727cd --- /dev/null +++ b/src/atlasComponents/sapiViews/features/voi/module.ts @@ -0,0 +1,19 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SAPIModule } from "src/atlasComponents/sapi/module"; +import { SapiViewsFeaturesVoiQuery } from "./voiQuery.directive"; + +@NgModule({ + imports: [ + CommonModule, + SAPIModule, + ], + declarations: [ + SapiViewsFeaturesVoiQuery, + ], + exports: [ + SapiViewsFeaturesVoiQuery + ] +}) + +export class SapiViewsFeaturesVoiModule{} diff --git a/src/atlasComponents/sapiViews/features/voi/voiQuery.directive.ts b/src/atlasComponents/sapiViews/features/voi/voiQuery.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..623011d89b1d2385fec8ca30c3456082056128e7 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/voi/voiQuery.directive.ts @@ -0,0 +1,190 @@ +import { Directive, EventEmitter, Inject, Input, OnChanges, OnDestroy, Optional, Output, SimpleChanges } from "@angular/core"; +import { interval, merge, Observable, of, Subject, Subscription } from "rxjs"; +import { debounce, debounceTime, distinctUntilChanged, filter, pairwise, shareReplay, startWith, switchMap, take, tap } from "rxjs/operators"; +import { AnnotationLayer, TNgAnnotationPoint, TNgAnnotationAABBox } from "src/atlasComponents/annotations"; +import { SAPI } from "src/atlasComponents/sapi/sapi.service"; +import { BoundingBoxConcept, SapiAtlasModel, SapiSpaceModel, SapiVOIDataResponse, OpenMINDSCoordinatePoint } from "src/atlasComponents/sapi/type"; +import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; +import { arrayEqual } from "src/util/array"; + +@Directive({ + selector: '[sxplr-sapiviews-features-voi-query]', + exportAs: 'sxplrSapiViewsFeaturesVoiQuery' +}) + +export class SapiViewsFeaturesVoiQuery implements OnChanges, OnDestroy{ + + static VOI_LAYER_NAME = 'voi-annotation-layer' + static VOI_ANNOTATION_COLOR = "#ffff00" + private voiQuerySpec = new Subject<{ + atlas: SapiAtlasModel + space: SapiSpaceModel + bbox: BoundingBoxConcept + }>() + + private canFetchVoi(){ + return !!this.atlas && !!this.space && !!this.bbox + } + + @Input('sxplr-sapiviews-features-voi-query-atlas') + atlas: SapiAtlasModel + + @Input('sxplr-sapiviews-features-voi-query-space') + space: SapiSpaceModel + + @Input('sxplr-sapiviews-features-voi-query-bbox') + bbox: BoundingBoxConcept + + @Output('sxplr-sapiviews-features-voi-query-onhover') + onhover = new EventEmitter<SapiVOIDataResponse>() + + @Output('sxplr-sapiviews-features-voi-query-onclick') + onclick = new EventEmitter<SapiVOIDataResponse>() + + public busy$ = new EventEmitter<boolean>() + public features$: Observable<SapiVOIDataResponse[]> = this.voiQuerySpec.pipe( + debounceTime(160), + tap(() => this.busy$.emit(true)), + switchMap(({ atlas, bbox, space }) => { + if (!this.canFetchVoi()) { + return of([]) + } + return merge( + of([]), + this.sapi.getSpace(atlas["@id"], space["@id"]).getFeatures({ bbox: JSON.stringify(bbox) }).pipe( + tap(val => { + this.busy$.emit(false) + }) + ) + ) + }), + startWith([]), + shareReplay(1) + ) + + private hoveredFeat: SapiVOIDataResponse + private onDestroyCb: (() => void)[] = [] + private subscription: Subscription[] = [] + ngOnChanges(simpleChanges: SimpleChanges): void { + if (simpleChanges.space) { + this.voiBBoxSvc = null + } + const { + atlas, + space, + bbox + } = this + this.voiQuerySpec.next({ atlas, space, bbox }) + } + ngOnDestroy(): void { + if (this.voiBBoxSvc) this.voiBBoxSvc.dispose() + while (this.subscription.length > 0) this.subscription.pop().unsubscribe() + while (this.voiSubs.length > 0) this.voiSubs.pop().unsubscribe() + while(this.onDestroyCb.length > 0) this.onDestroyCb.pop()() + } + + handleOnHoverFeature({ id }: { id?: string }){ + const ann = this.annotationIdToFeature.get(id) + this.hoveredFeat = ann + this.onhover.emit(ann) + } + + private voiSubs: Subscription[] = [] + private _voiBBoxSvc: AnnotationLayer + get voiBBoxSvc(): AnnotationLayer { + if (this._voiBBoxSvc) return this._voiBBoxSvc + try { + const layer = AnnotationLayer.Get( + SapiViewsFeaturesVoiQuery.VOI_LAYER_NAME, + SapiViewsFeaturesVoiQuery.VOI_ANNOTATION_COLOR + ) + this._voiBBoxSvc = layer + this.voiSubs.push( + layer.onHover.subscribe(val => this.handleOnHoverFeature(val || {})) + ) + return layer + } catch (e) { + return null + } + } + set voiBBoxSvc(val) { + if (!!val) { + throw new Error(`cannot set voiBBoxSvc directly`) + } + while (this.voiSubs.length > 0) this.voiSubs.pop().unsubscribe() + this._voiBBoxSvc && this._voiBBoxSvc.dispose() + this._voiBBoxSvc = null + } + + constructor( + private sapi: SAPI, + @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor, + ){ + const handle = () => { + if (!this.hoveredFeat) return true + this.onclick.emit(this.hoveredFeat) + return false + } + this.onDestroyCb.push( + () => clickInterceptor.deregister(handle) + ) + clickInterceptor.register(handle) + this.subscription.push( + this.features$.pipe( + startWith([] as SapiVOIDataResponse[]), + distinctUntilChanged(arrayEqual((o, n) => o["@id"] === n["@id"])), + pairwise(), + debounce(() => + interval(16).pipe( + filter(() => !!this.voiBBoxSvc), + take(1), + ) + ), + ).subscribe(([ prev, curr ]) => { + for (const v of prev) { + const box = this.pointsToAABB(v.location.maxpoint, v.location.minpoint) + const point = this.pointToPoint(v.location.center) + this.annotationIdToFeature.delete(box.id) + this.annotationIdToFeature.delete(point.id) + if (!this.voiBBoxSvc) continue + for (const ann of [box, point]) { + this.voiBBoxSvc.removeAnnotation({ + id: ann.id + }) + } + } + for (const v of curr) { + const box = this.pointsToAABB(v.location.maxpoint, v.location.minpoint) + const point = this.pointToPoint(v.location.center) + this.annotationIdToFeature.set(box.id, v) + this.annotationIdToFeature.set(point.id, v) + if (!this.voiBBoxSvc) { + throw new Error(`annotation is expected to be added, but annotation layer cannot be instantiated.`) + } + for (const ann of [box, point]) { + this.voiBBoxSvc.updateAnnotation(ann) + } + } + if (this.voiBBoxSvc) this.voiBBoxSvc.setVisible(true) + }) + ) + } + + private annotationIdToFeature = new Map<string, SapiVOIDataResponse>() + + private pointsToAABB(pointA: OpenMINDSCoordinatePoint, pointB: OpenMINDSCoordinatePoint): TNgAnnotationAABBox{ + return { + id: `${SapiViewsFeaturesVoiQuery.VOI_LAYER_NAME}:${pointA["@id"]}:${pointB["@id"]}`, + pointA: pointA.coordinates.map(v => v.value * 1e6) as [number, number, number], + pointB: pointB.coordinates.map(v => v.value * 1e6) as [number, number, number], + type: "aabbox" + } + } + private pointToPoint(point: OpenMINDSCoordinatePoint): TNgAnnotationPoint { + return { + id: `${SapiViewsFeaturesVoiQuery.VOI_LAYER_NAME}:${point["@id"]}`, + point: point.coordinates.map(v => v.value * 1e6) as [number, number, number], + type: "point" + } + } +} diff --git a/src/atlasComponents/sapiViews/index.ts b/src/atlasComponents/sapiViews/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..9fdab95aa0bcf8ede9d104573c674ec8f243483e --- /dev/null +++ b/src/atlasComponents/sapiViews/index.ts @@ -0,0 +1,4 @@ +export { + SapiViewsModule +} from "./module" +export { SapiViewsUtilModule } from "./util" \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/module.ts b/src/atlasComponents/sapiViews/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff029f0362be7205bec3a45957debcc25f87197c --- /dev/null +++ b/src/atlasComponents/sapiViews/module.ts @@ -0,0 +1,15 @@ +import { NgModule } from "@angular/core"; +import { SapiViewsCoreModule } from "./core"; +import { SapiViewsFeaturesModule } from "./features"; + +@NgModule({ + imports: [ + SapiViewsFeaturesModule, + SapiViewsCoreModule, + ], + exports: [ + SapiViewsFeaturesModule, + SapiViewsCoreModule, + ] +}) +export class SapiViewsModule{} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/richDataset.stories.ts b/src/atlasComponents/sapiViews/richDataset.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..03ac405b682240aedd06eac97db2b8d659e55d00 --- /dev/null +++ b/src/atlasComponents/sapiViews/richDataset.stories.ts @@ -0,0 +1,138 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Component } from "@angular/core" +import { provideMockStore } from "@ngrx/store/testing" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI } from "src/atlasComponents/sapi" +import { getHoc1RightFeatureDetail, getHoc1RightFeatures, getHoc1Right, getHumanAtlas, getJba29, getMni152, provideDarkTheme, getMni152SpatialFeatureHoc1Right } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { cleanIeegSessionDatasets, SapiAtlasModel, SapiFeatureModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "../sapi/type" +import { SapiViewsCoreDatasetModule } from "./core/datasets" +import { SapiViewsFeaturesModule } from "./features" + +@Component({ + selector: `rich-dataset-wrapper-cmp`, + template: ` + <div *ngIf="!dataset"> + Dataset must be provided + </div> + <mat-card *ngIf="dataset"> + <sxplr-sapiviews-core-datasets-dataset + [sxplr-sapiviews-core-datasets-dataset-input]="dataset"> + </sxplr-sapiviews-core-datasets-dataset> + + <sxplr-sapiviews-features-entry + [sxplr-sapiviews-features-entry-atlas]="atlas" + [sxplr-sapiviews-features-entry-space]="template" + [sxplr-sapiviews-features-entry-parcellation]="parcellation" + [sxplr-sapiviews-features-entry-region]="region" + [sxplr-sapiviews-features-entry-feature]="dataset"> + </sxplr-sapiviews-features-entry> + </mat-card> + + `, + styles: [ + `mat-card { max-width: 40rem; }` + ] +}) + +class RichDatasetWrapperCmp { + atlas: SapiAtlasModel + parcellation: SapiParcellationModel + template: SapiSpaceModel + region: SapiRegionModel + dataset: SapiFeatureModel +} + +export default { + component: RichDatasetWrapperCmp, + decorators: [ + moduleMetadata({ + imports: [ + AngularMaterialModule, + CommonModule, + HttpClientModule, + SapiViewsCoreDatasetModule, + SapiViewsFeaturesModule, + ], + providers: [ + SAPI, + provideMockStore(), + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<RichDatasetWrapperCmp> = (args: RichDatasetWrapperCmp, { loaded }) => { + const { + + atlas, + parcellation, + template, + region, + feature + } = loaded + return ({ + props: { + atlas, + parcellation, + template, + region, + dataset: feature, + }, + }) +} + +const loadRegionMetadata = async () => { + + const atlas = await getHumanAtlas() + const parcellation = await getJba29() + const template = await getMni152() + const region = await getHoc1Right() + return { + atlas, + parcellation, + template, + region, + } +} + +const loadFeat = async () => { + const features = await getHoc1RightFeatures() + return { features } +} + +export const ReceptorDataset = Template.bind({}) +ReceptorDataset.args = { + +} +ReceptorDataset.loaders = [ + async () => { + return await loadRegionMetadata() + }, + async () => { + const { features } = await loadFeat() + const receptorfeat = features.find(f => f['@type'] === "siibra/features/receptor") + const feature = await getHoc1RightFeatureDetail(receptorfeat["@id"]) + return { feature } + } +] + +export const IeegDataset = Template.bind({}) +IeegDataset.args = { + +} +IeegDataset.loaders = [ + async () => { + return await loadRegionMetadata() + }, + async () => { + const features = await getMni152SpatialFeatureHoc1Right() + const spatialFeats = features.filter(f => f["@type"] === "siibra/features/ieegSession") + const feature = cleanIeegSessionDatasets(spatialFeats)[0] + + return { feature } + } +] diff --git a/src/util/pipes/addUnitAndJoin.pipe.ts b/src/atlasComponents/sapiViews/util/addUnitAndJoin.pipe.ts similarity index 100% rename from src/util/pipes/addUnitAndJoin.pipe.ts rename to src/atlasComponents/sapiViews/util/addUnitAndJoin.pipe.ts diff --git a/src/atlasComponents/sapiViews/util/equality.pipe.ts b/src/atlasComponents/sapiViews/util/equality.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..8251d641fed51ac99384c6470af5623e0c37451c --- /dev/null +++ b/src/atlasComponents/sapiViews/util/equality.pipe.ts @@ -0,0 +1,16 @@ +import { Pipe } from "@angular/core"; + +type TTrackBy<T, O> = (input: T) => O + +const defaultTrackBy: TTrackBy<unknown, unknown> = i => i + +@Pipe({ + name: 'equality', + pure: true +}) + +export class EqualityPipe<T>{ + public transform(c1: T, c2: T, trackBy: TTrackBy<T, unknown> = defaultTrackBy): boolean { + return trackBy(c1) === trackBy(c2) + } +} diff --git a/src/util/pipes/includes.pipe.ts b/src/atlasComponents/sapiViews/util/includes.pipe.ts similarity index 100% rename from src/util/pipes/includes.pipe.ts rename to src/atlasComponents/sapiViews/util/includes.pipe.ts diff --git a/src/atlasComponents/sapiViews/util/index.ts b/src/atlasComponents/sapiViews/util/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..36e2c654710830f0df9fdbbf0ea024f6a200545c --- /dev/null +++ b/src/atlasComponents/sapiViews/util/index.ts @@ -0,0 +1 @@ +export { SapiViewsUtilModule } from "./module" \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/util/module.ts b/src/atlasComponents/sapiViews/util/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..53a3a88c549821dfe9a7ee654c93dac7747cd368 --- /dev/null +++ b/src/atlasComponents/sapiViews/util/module.ts @@ -0,0 +1,34 @@ +import { NgModule } from "@angular/core"; +import { AddUnitAndJoin } from "./addUnitAndJoin.pipe"; +import { EqualityPipe } from "./equality.pipe"; +import { IncludesPipe } from "./includes.pipe"; +import { NumbersPipe } from "./numbers.pipe"; +import { ParcellationSupportedInCurrentSpace } from "./parcellationSupportedInCurrentSpace.pipe"; +import { ParcellationSupportedInSpacePipe } from "./parcellationSupportedInSpace.pipe"; +import { ParseDoiPipe } from "./parseDoi.pipe"; +import { SpaceSupportedInCurrentParcellationPipe } from "./spaceSupportedInCurrentParcellation.pipe"; + +@NgModule({ + declarations: [ + EqualityPipe, + ParseDoiPipe, + NumbersPipe, + AddUnitAndJoin, + IncludesPipe, + ParcellationSupportedInSpacePipe, + ParcellationSupportedInCurrentSpace, + SpaceSupportedInCurrentParcellationPipe, + ], + exports: [ + EqualityPipe, + ParseDoiPipe, + NumbersPipe, + AddUnitAndJoin, + IncludesPipe, + ParcellationSupportedInSpacePipe, + ParcellationSupportedInCurrentSpace, + SpaceSupportedInCurrentParcellationPipe, + ] +}) + +export class SapiViewsUtilModule{} \ No newline at end of file diff --git a/src/util/pipes/numbers.pipe.ts b/src/atlasComponents/sapiViews/util/numbers.pipe.ts similarity index 52% rename from src/util/pipes/numbers.pipe.ts rename to src/atlasComponents/sapiViews/util/numbers.pipe.ts index 0390ea90d1a526faa421629fbe01911a5e6832a1..e96164a92178de9fa8f7d28fcf28331739fb496b 100644 --- a/src/util/pipes/numbers.pipe.ts +++ b/src/atlasComponents/sapiViews/util/numbers.pipe.ts @@ -1,12 +1,12 @@ import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ - name: 'nmToMm', + name: 'numbers', pure: true }) -export class NmToMm implements PipeTransform{ +export class NumbersPipe implements PipeTransform{ public transform(nums: number[], decimal: number = 2): number[] { - return nums.map(num => (num / 1e6).toFixed(decimal)).map(Number) + return nums.map(num => num.toFixed(decimal)).map(Number) } -} \ No newline at end of file +} diff --git a/src/atlasComponents/sapiViews/util/parcellationSupportedInCurrentSpace.pipe.ts b/src/atlasComponents/sapiViews/util/parcellationSupportedInCurrentSpace.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..3fd1069f0b13c69822d115aba804ff93e81c6c27 --- /dev/null +++ b/src/atlasComponents/sapiViews/util/parcellationSupportedInCurrentSpace.pipe.ts @@ -0,0 +1,37 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { select, Store } from "@ngrx/store"; +import { Observable } from "rxjs"; +import { switchMap } from "rxjs/operators"; +import { SAPI } from "src/atlasComponents/sapi/sapi.service"; +import { SapiParcellationModel } from "src/atlasComponents/sapi/type"; +import { atlasSelection } from "src/state"; +import { ParcellationSupportedInSpacePipe } from "./parcellationSupportedInSpace.pipe" + +@Pipe({ + name: 'parcellationSupportedInCurrentSpace', + /** + * the pipe is not exactly pure, since it makes http call + * but for the sake of angular change detection, this is suitable + * since the result should only change on input change + */ + pure: true +}) + +export class ParcellationSupportedInCurrentSpace implements PipeTransform{ + + private transformPipe = new ParcellationSupportedInSpacePipe(this.sapi) + + private selectedTemplate$ = this.store.pipe( + select(atlasSelection.selectors.selectedTemplate) + ) + constructor( + private store: Store, + private sapi: SAPI, + ){} + + public transform(parcellation: SapiParcellationModel): Observable<boolean> { + return this.selectedTemplate$.pipe( + switchMap(tmpl => this.transformPipe.transform(parcellation, tmpl)) + ) + } +} diff --git a/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts b/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..f16e6a0a82d991ca8955e84f9ed4a15fe14e2ab4 --- /dev/null +++ b/src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe.ts @@ -0,0 +1,47 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { Observable, of } from "rxjs"; +import { map } from "rxjs/operators"; +import { SAPIParcellation } from "src/atlasComponents/sapi/core"; +import { SAPI } from "src/atlasComponents/sapi/sapi.service"; +import { SapiParcellationModel, SapiSpaceModel } from "src/atlasComponents/sapi/type"; + +export const knownExceptions = { + supported: { + /** + * jba29 + */ + 'minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290': [ + /** + * big brain + */ + 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588' + ] + } +} + +@Pipe({ + name: 'parcellationSuppportedInSpace', + pure: false, +}) + +export class ParcellationSupportedInSpacePipe implements PipeTransform{ + + constructor(private sapi: SAPI){} + + public transform(parc: SapiParcellationModel|string, tmpl: SapiSpaceModel|string): Observable<boolean> { + const parcId = typeof parc === "string" + ? parc + : parc["@id"] + const tmplId = typeof tmpl === "string" + ? tmpl + : tmpl["@id"] + for (const key in knownExceptions.supported) { + if (key === parcId && knownExceptions.supported[key].indexOf(tmplId) >= 0) { + return of(true) + } + } + return this.sapi.registry.get<SAPIParcellation>(parcId).getVolumes().pipe( + map(volumes => volumes.some(v => v.data.space["@id"] === tmplId)) + ) + } +} diff --git a/src/atlasComponents/sapiViews/util/parseDoi.pipe.spec.ts b/src/atlasComponents/sapiViews/util/parseDoi.pipe.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..866f3c40cad50af75fa45abfe849f8af9a2e5712 --- /dev/null +++ b/src/atlasComponents/sapiViews/util/parseDoi.pipe.spec.ts @@ -0,0 +1,17 @@ +import {} from 'jasmine' +import { ParseDoiPipe } from './parseDoi.pipe' + +describe('doiPipe.pipe.ts', () => { + const pipe = new ParseDoiPipe() + describe('DoiParsePIpe' , () => { + it('should parse string without prefix by appending doi prefix', () => { + const result = pipe.transform('123.456') + expect(result).toBe(`https://doi.org/123.456`) + }) + + it('should not append doi prefix if the first argument leads by http or https', () => { + expect(pipe.transform('http://google.com')).toBe('http://google.com') + expect(pipe.transform('https://google.com')).toBe('https://google.com') + }) + }) +}) diff --git a/src/atlasComponents/sapiViews/util/parseDoi.pipe.ts b/src/atlasComponents/sapiViews/util/parseDoi.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a4d87cbc9bb37216e379e5e80a4360eacfb055a --- /dev/null +++ b/src/atlasComponents/sapiViews/util/parseDoi.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from "@angular/core"; + +@Pipe({ + name: 'parseDoi', +}) + +export class ParseDoiPipe implements PipeTransform { + public transform(s: string, prefix: string = 'https://doi.org/') { + const hasProtocol = /^https?:\/\//.test(s) + return `${hasProtocol ? '' : prefix}${s}` + } +} diff --git a/src/atlasComponents/sapiViews/util/spaceSupportedInCurrentParcellation.pipe.ts b/src/atlasComponents/sapiViews/util/spaceSupportedInCurrentParcellation.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..e462fcc6bf41fc412852a9ef8d3404023eff96d6 --- /dev/null +++ b/src/atlasComponents/sapiViews/util/spaceSupportedInCurrentParcellation.pipe.ts @@ -0,0 +1,38 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { select, Store } from "@ngrx/store"; +import { Observable } from "rxjs"; +import { switchMap } from "rxjs/operators"; +import { SAPI } from "src/atlasComponents/sapi/sapi.service"; +import { SapiSpaceModel } from "src/atlasComponents/sapi/type"; +import { atlasSelection } from "src/state"; +import { ParcellationSupportedInSpacePipe } from "./parcellationSupportedInSpace.pipe" + +@Pipe({ + name: "spaceSupportedInCurrentParcellation", + /** + * the pipe is not exactly pure, since it makes http call + * but for the sake of angular change detection, this is suitable + * since the result should only change on input change + */ + pure: true +}) + +export class SpaceSupportedInCurrentParcellationPipe implements PipeTransform{ + private supportedPipe = new ParcellationSupportedInSpacePipe(this.sapi) + private selectedParcellation$ = this.store.pipe( + select(atlasSelection.selectors.selectedParcellation) + ) + constructor( + private store: Store, + private sapi: SAPI + ){ + + } + public transform(space: SapiSpaceModel): Observable<boolean> { + return this.selectedParcellation$.pipe( + switchMap(parc => + this.supportedPipe.transform(parc, space) + ) + ) + } +} diff --git a/src/atlasComponents/splashScreen/index.ts b/src/atlasComponents/splashScreen/index.ts deleted file mode 100644 index 5927a7b64a7f19529a4998770dd5f27a3d3a2546..0000000000000000000000000000000000000000 --- a/src/atlasComponents/splashScreen/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { GetTemplateImageSrcPipe, SplashScreen } from "./splashScreen/splashScreen.component"; -export { SplashUiModule } from './module' \ No newline at end of file diff --git a/src/atlasComponents/splashScreen/module.ts b/src/atlasComponents/splashScreen/module.ts deleted file mode 100644 index cafa61559313528ffe6a1e74c2b4a88c240f8329..0000000000000000000000000000000000000000 --- a/src/atlasComponents/splashScreen/module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { ComponentsModule } from "src/components"; -import { KgTosModule } from "src/ui/kgtos/module"; -import { AngularMaterialModule } from "src/sharedModules"; -import { UtilModule } from "src/util"; -import { GetTemplateImageSrcPipe, SplashScreen } from "./splashScreen/splashScreen.component"; - -@NgModule({ - imports: [ - AngularMaterialModule, - CommonModule, - UtilModule, - KgTosModule, - ComponentsModule, - ], - declarations: [ - SplashScreen, - GetTemplateImageSrcPipe, - ], - exports: [ - SplashScreen, - ] -}) - -export class SplashUiModule{} \ No newline at end of file diff --git a/src/atlasComponents/splashScreen/splashScreen/splashScreen.component.ts b/src/atlasComponents/splashScreen/splashScreen/splashScreen.component.ts deleted file mode 100644 index 3df8c71c099444e4be81158e3093c8c9e6d3dcde..0000000000000000000000000000000000000000 --- a/src/atlasComponents/splashScreen/splashScreen/splashScreen.component.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Pipe, PipeTransform, ViewChild } from "@angular/core"; -import { MatSnackBar } from "@angular/material/snack-bar"; -import { select, Store } from "@ngrx/store"; -import { Observable, Subject, Subscription } from "rxjs"; -import { filter } from 'rxjs/operators' -import { viewerStateHelperStoreName, viewerStateSelectAtlas } from "src/services/state/viewerState.store.helper"; -import { PureContantService } from "src/util"; -import { CONST } from 'common/constants' - -@Component({ - selector : 'ui-splashscreen', - templateUrl : './splashScreen.template.html', - styleUrls : [ - `./splashScreen.style.css`, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) - -export class SplashScreen { - - public finishedLoading: boolean = false - - public loadedAtlases$: Observable<any[]> - - public filterNullFn(atlas: any){ - return !!atlas - } - - @ViewChild('parentContainer', {read: ElementRef}) - public activatedTemplate$: Subject<any> = new Subject() - - private subscriptions: Subscription[] = [] - - constructor( - private store: Store<any>, - private snack: MatSnackBar, - private pureConstantService: PureContantService, - private cdr: ChangeDetectorRef, - ) { - this.subscriptions.push( - this.pureConstantService.allFetchingReady$.subscribe(flag => { - this.finishedLoading = flag - this.cdr.markForCheck() - }) - ) - - this.loadedAtlases$ = this.store.pipe( - select(state => state[viewerStateHelperStoreName]), - select(state => state.fetchedAtlases), - filter(v => !!v) - ) - } - - public selectAtlas(atlas: any){ - if (!this.finishedLoading) { - this.snack.open(CONST.DATA_NOT_READY, null, { - duration: 3000 - }) - return - } - this.store.dispatch( - viewerStateSelectAtlas({ atlas }) - ) - } -} - -@Pipe({ - name: 'getTemplateImageSrcPipe', -}) - -export class GetTemplateImageSrcPipe implements PipeTransform { - public transform(name: string): string { - return `./res/image/${name.replace(/[|&;$%@()+,\s./]/g, '')}.png` - } -} diff --git a/src/atlasComponents/template/index.ts b/src/atlasComponents/template/index.ts deleted file mode 100644 index 38cd6578cc518656abb1303d11557b86529cc996..0000000000000000000000000000000000000000 --- a/src/atlasComponents/template/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { GetTemplatePreviewUrlPipe } from "./getTemplatePreviewUrl.pipe"; -export { SiibraExplorerTemplateModule } from './module' diff --git a/src/atlasComponents/template/module.ts b/src/atlasComponents/template/module.ts deleted file mode 100644 index 9f1a49646b274dc7936b24800f99a4bf12e152c7..0000000000000000000000000000000000000000 --- a/src/atlasComponents/template/module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { NgModule } from "@angular/core"; -import { GetTemplatePreviewUrlPipe } from "./getTemplatePreviewUrl.pipe"; -import { TemplateIsDarkThemePipe } from "./templateIsDarkTheme.pipe"; - -@NgModule({ - imports: [], - declarations: [ - GetTemplatePreviewUrlPipe, - TemplateIsDarkThemePipe, - ], - exports: [ - GetTemplatePreviewUrlPipe, - TemplateIsDarkThemePipe, - ] -}) - -export class SiibraExplorerTemplateModule{} \ No newline at end of file diff --git a/src/atlasComponents/template/templateIsDarkTheme.pipe.ts b/src/atlasComponents/template/templateIsDarkTheme.pipe.ts deleted file mode 100644 index 8454fd96e81e9815f8ab0bb627eb1e01d18b95b0..0000000000000000000000000000000000000000 --- a/src/atlasComponents/template/templateIsDarkTheme.pipe.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { OnDestroy, Pipe, PipeTransform } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { Subscription } from "rxjs"; -import { viewerStateSelectedTemplateFullInfoSelector } from "src/services/state/viewerState/selectors"; -import { IHasId } from "src/util/interfaces"; - -@Pipe({ - name: 'templateIsDarkTheme', - pure: true, -}) - -export class TemplateIsDarkThemePipe implements OnDestroy, PipeTransform{ - - private templateFullInfo: any[] = [] - constructor(store: Store<any>){ - this.sub.push( - store.pipe( - select(viewerStateSelectedTemplateFullInfoSelector) - ).subscribe(val => this.templateFullInfo = val) - ) - } - - private sub: Subscription[] = [] - - ngOnDestroy(){ - while(this.sub.length) this.sub.pop().unsubscribe() - } - - public transform(template: IHasId): boolean{ - const found = this.templateFullInfo.find(t => t['@id'] === template["@id"]) - return found && found.darktheme - } -} \ No newline at end of file diff --git a/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.component.spec.ts b/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.component.spec.ts deleted file mode 100644 index 70b786d12ed055a08b57f5cf47f717bf6a266301..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.component.spec.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO diff --git a/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.component.ts b/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.component.ts deleted file mode 100644 index c44816155bcbb23701aa04e7a9de767b70dd056f..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/atlasDropdown/atlasDropdown.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Component } from "@angular/core"; -import { Store, select } from "@ngrx/store"; -import { Observable } from "rxjs"; -import { distinctUntilChanged } from "rxjs/operators"; -import { viewerStateHelperStoreName, viewerStateSelectAtlas } from "src/services/state/viewerState.store.helper"; -import { ARIA_LABELS } from 'common/constants' -import { viewerStateGetSelectedAtlas } from "src/services/state/viewerState/selectors"; - -@Component({ - selector: 'atlas-dropdown-selector', - templateUrl: './atlasDropdown.template.html', - styleUrls: [ - './atlasDropdown.style.css' - ] -}) - -export class AtlasDropdownSelector{ - - public fetchedAtlases$: Observable<any[]> - public selectedAtlas$: Observable<any> - - public SELECT_ATLAS_ARIA_LABEL = ARIA_LABELS.SELECT_ATLAS - - constructor(private store$: Store<any>){ - this.fetchedAtlases$ = this.store$.pipe( - select(viewerStateHelperStoreName), - select('fetchedAtlases'), - distinctUntilChanged() - ) - this.selectedAtlas$ = this.store$.pipe( - select(viewerStateGetSelectedAtlas) - ) - } - - handleChangeAtlas({ value }) { - this.store$.dispatch( - viewerStateSelectAtlas({ - atlas: { - ['@id']: value - } - }) - ) - } -} - diff --git a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.spec.ts b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.spec.ts deleted file mode 100644 index 730f7b258819bf92a3a678976bead30a75817452..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { CommonModule } from "@angular/common" -import { ComponentFixture, TestBed } from "@angular/core/testing" -import { NoopAnimationsModule } from "@angular/platform-browser/animations" -import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { ComponentsModule } from "src/components" -import { viewerStateSelectTemplateWithId, viewerStateToggleLayer } from "src/services/state/viewerState.store.helper" -import { AngularMaterialModule } from "src/sharedModules" -import { QuickTourModule } from "src/ui/quickTour" -import { GetGroupedParcPipe } from "../pipes/getGroupedParc.pipe" -import { GetIndividualParcPipe } from "../pipes/getIndividualParc.pipe" -import { GetNonbaseParcPipe } from "../pipes/getNonBaseParc.pipe" -import { AtlasLayerSelector } from "./atlasLayerSelector.component" - -describe("> atlasLayerSelector.component.ts", () => { - describe("> AtlasLayerSelector", () => { - let fixture: ComponentFixture<AtlasLayerSelector> - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - CommonModule, - AngularMaterialModule, - NoopAnimationsModule, - QuickTourModule, - ComponentsModule, - ], - declarations: [ - AtlasLayerSelector, - GetIndividualParcPipe, - GetNonbaseParcPipe, - GetGroupedParcPipe, - ], - providers: [ - provideMockStore() - ] - }).compileComponents() - }) - - it("> can be init", () => { - fixture = TestBed.createComponent(AtlasLayerSelector) - expect(fixture).toBeTruthy() - }) - - describe("> methods", () => { - beforeEach(() => { - fixture = TestBed.createComponent(AtlasLayerSelector) - }) - describe("> selectParcellationWithName", () => { - let tmplSupportParcPipeTransform: jasmine.Spy - let storeDispatchSpy: jasmine.Spy - const dummySelectedTmpl = { - "this": "obj" - } - const dummyParc = { - "foo": "bar", - "availableIn": [{ - "baz": "yoo" - }] - } - beforeEach(() => { - tmplSupportParcPipeTransform = spyOn(fixture.componentInstance['currTmplSupportParcPipe'], 'transform') - const store = TestBed.inject(MockStore) - storeDispatchSpy = spyOn(store, 'dispatch') - fixture.componentInstance['selectedTemplate'] = dummySelectedTmpl - }) - afterEach(() => { - if (tmplSupportParcPipeTransform) { - tmplSupportParcPipeTransform.calls.reset() - } - }) - it("> calls pipe.transform", () => { - tmplSupportParcPipeTransform.and.returnValue(true) - fixture.componentInstance.selectParcellationWithName(dummyParc) - expect(tmplSupportParcPipeTransform).toHaveBeenCalledWith(dummySelectedTmpl, dummyParc) - expect(storeDispatchSpy).toHaveBeenCalledTimes(1) - }) - - const tmplChgReqParam = [true, false] - - for (const tmplChgReq of tmplChgReqParam) { - describe(`> tmplChgReq: ${tmplChgReq}`, () => { - it("> expect the correct type of method to be called", () => { - tmplSupportParcPipeTransform.and.returnValue(!tmplChgReq) - fixture.componentInstance.selectParcellationWithName(dummyParc) - const allArgs = storeDispatchSpy.calls.allArgs() - expect(allArgs.length).toEqual(1) - expect(allArgs[0].length).toEqual(1) - const action = allArgs[0][0] - - const expectedAction = tmplChgReq - ? viewerStateSelectTemplateWithId - : viewerStateToggleLayer - - expect(action.type).toEqual(expectedAction.type) - }) - }) - } - }) - }) - }) -}) diff --git a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts deleted file mode 100644 index 9fbca8cce700ba9ef5bef01581a618e451308df3..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.component.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { Component, OnInit, ViewChildren, QueryList, HostBinding, ViewChild, ElementRef, OnDestroy } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { distinctUntilChanged, map, withLatestFrom, shareReplay, mapTo } from "rxjs/operators"; -import { merge, Observable, Subject, Subscription } from "rxjs"; -import { viewerStateSelectTemplateWithId, viewerStateToggleLayer } from "src/services/state/viewerState.store.helper"; -import { MatMenuTrigger } from "@angular/material/menu"; -import { viewerStateGetSelectedAtlas, viewerStateAtlasLatestParcellationSelector, viewerStateSelectedTemplateFullInfoSelector, viewerStateSelectedTemplatePureSelector, viewerStateSelectedParcellationSelector } from "src/services/state/viewerState/selectors"; -import { ARIA_LABELS, CONST, QUICKTOUR_DESC } from 'common/constants' -import { IQuickTourData } from "src/ui/quickTour/constrants"; -import { animate, state, style, transition, trigger } from "@angular/animations"; -import { IHasId, OVERWRITE_SHOW_DATASET_DIALOG_TOKEN } from "src/util/interfaces"; -import { CurrentTmplSupportsParcPipe } from "../pipes/currTmplSupportsParc.pipe"; - -@Component({ - selector: 'atlas-layer-selector', - templateUrl: './atlasLayerSelector.template.html', - styleUrls: ['./atlasLayerSelector.style.css'], - exportAs: 'atlasLayerSelector', - animations: [ - trigger('toggleAtlasLayerSelector', [ - state('false', style({ - transform: 'scale(0)', - opacity: 0, - transformOrigin: '0% 100%' - })), - state('true', style({ - transform: 'scale(1)', - opacity: 1, - transformOrigin: '0% 100%' - })), - transition('false => true', [ - animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') - ]), - transition('true => false', [ - animate('200ms cubic-bezier(0.35, 0, 0.25, 1)') - ]) - ]) - ], - providers:[ - { - provide: OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, - useValue: null - } - ] -}) -export class AtlasLayerSelector implements OnInit, OnDestroy { - - public ARIA_LABELS = ARIA_LABELS - public CONST = CONST - - @ViewChildren(MatMenuTrigger) - matMenuTriggers: QueryList<MatMenuTrigger> - - @ViewChild('selectorPanelTmpl', { read: ElementRef }) - selectorPanelTemplateRef: ElementRef - - public selectedAtlas$: Observable<any> = this.store$.pipe( - select(viewerStateGetSelectedAtlas), - distinctUntilChanged(), - shareReplay(1) - ) - - public atlasLayersLatest$ = this.store$.pipe( - select(viewerStateAtlasLatestParcellationSelector), - shareReplay(1), - ) - - public availableTemplates$ = this.store$.pipe<any[]>( - select(viewerStateSelectedTemplateFullInfoSelector), - ) - - private selectedTemplate: any - public selectedTemplate$ = this.store$.pipe( - select(viewerStateSelectedTemplatePureSelector), - withLatestFrom(this.availableTemplates$), - map(([selectedTmpl, fullInfoTemplates]) => { - return fullInfoTemplates.find(t => t['@id'] === selectedTmpl['@id']) - }) - ) - private showOverlayIntent$ = new Subject() - public showLoadingOverlay$ = merge( - this.showOverlayIntent$.pipe( - mapTo(true) - ), - this.selectedTemplate$.pipe( - mapTo(false) - ) - ).pipe( - distinctUntilChanged(), - ) - - public selectedParcellation$ = this.store$.pipe( - select(viewerStateSelectedParcellationSelector), - ) - - private subscriptions: Subscription[] = [] - - @HostBinding('attr.data-opened') - public selectorExpanded: boolean = false - - public quickTourData: IQuickTourData = { - order: 4, - description: QUICKTOUR_DESC.LAYER_SELECTOR, - } - - constructor(private store$: Store<any>) {} - - ngOnInit(): void { - this.subscriptions.push( - this.selectedTemplate$.subscribe(st => { - this.selectedTemplate = st - }), - ) - } - - ngOnDestroy() { - while(this.subscriptions.length) this.subscriptions.pop().unsubscribe() - } - - - toggleSelector() { - this.selectorExpanded = !this.selectorExpanded - } - - selectTemplatewithId(templateId: string) { - this.showOverlayIntent$.next(true) - this.store$.dispatch(viewerStateSelectTemplateWithId({ - payload: { - '@id': templateId - } - })) - } - - private currTmplSupportParcPipe = new CurrentTmplSupportsParcPipe() - - selectParcellationWithName(layer: any) { - const tmplChangeReq = !this.currTmplSupportParcPipe.transform(this.selectedTemplate, layer) - if (!tmplChangeReq) { - this.store$.dispatch( - viewerStateToggleLayer({ payload: layer }) - ) - } else { - this.showOverlayIntent$.next(true) - this.store$.dispatch( - viewerStateSelectTemplateWithId({ - payload: layer.availableIn[0], - config: { - selectParcellation: layer - } - }) - ) - } - } - - collapseExpandedGroup(){ - this.matMenuTriggers.forEach(t => t.menuOpen && t.closeMenu()) - } - - - trackbyAtId(t: IHasId){ - return t['@id'] - } - - trackKeyVal(obj: {key: string, value: any}) { - return obj.key - } -} diff --git a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html b/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html deleted file mode 100644 index ed78029cd9c644c84e4c809e9498ff21a5eece81..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/atlasLayerSelector/atlasLayerSelector.template.html +++ /dev/null @@ -1,171 +0,0 @@ -<!-- selector panel when expanded --> - -<mat-card class="selector-container m-2 position-absolute" - [ngClass]="{'pe-all': selectorExpanded}" - [@toggleAtlasLayerSelector]="selectorExpanded" - (@toggleAtlasLayerSelector.done)="atlasSelectorTour?.attachTo(selectorExpanded ? selectorPanelTemplateRef : null)" - #selectorPanelTmpl> - <mat-card-content> - - <!-- templates --> - <mat-card-subtitle> - {{ CONST.ATLAS_SELECTOR_LABEL_SPACES }} - </mat-card-subtitle> - - <!-- template grid and tiles --> - <mat-grid-list cols="3" - rowHeight="2:3" - gutterSize="16"> - - <mat-grid-tile *ngFor="let template of availableTemplates$ | async; trackBy: trackbyAtId" - [attr.aria-checked]="(selectedTemplate$ | async | getProperty : '@id') === template['@id']"> - - <div [hidden] - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-name]="template.originDatainfos[0]?.name" - [iav-dataset-show-dataset-dialog-description]="template.originDatainfos[0]?.description" - [iav-dataset-show-dataset-dialog-urls]="template.originDatainfos[0]?.urls" - #kgInfo="iavDatasetShowDatasetDialog"> - </div> - <tile-cmp [tile-image-src]="template | getPreviewUrlPipe" - class="cursor-pointer pe-all" - tile-image-alt="Preview of this tile" - [tile-text]="template.displayName || template.name" - [tile-show-info]="template.originDatainfos?.length > 0" - [tile-disabled]="!(selectedParcellation$ | async | currParcSupportsTmpl : template)" - [tile-image-darktheme]="template.darktheme" - [tile-selected]="(selectedTemplate$ | async | getProperty : '@id') === template['@id']" - (tile-on-click)="selectTemplatewithId(template['@id'])" - (tile-on-info-click)="kgInfo && kgInfo.onClick()"> - </tile-cmp> - </mat-grid-tile> - </mat-grid-list> - - <mat-divider></mat-divider> - - <!-- parcellations --> - <mat-card-subtitle class="mt-2"> - {{ CONST.ATLAS_SELECTOR_LABEL_PARC_MAPS }} - </mat-card-subtitle> - - <mat-grid-list cols="3" - rowHeight="2:3" - gutterSize="16"> - - <!-- non grouped layers --> - <mat-grid-tile *ngFor="let layer of (atlasLayersLatest$ | async | getNonbaseParc | getIndividualParc); trackBy: trackbyAtId" - [attr.aria-checked]="selectedParcellation$ | async | groupParcSelected : layer"> - - <div [hidden] - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-name]="layer.originDatainfos[0]?.name" - [iav-dataset-show-dataset-dialog-description]="layer.originDatainfos[0]?.description" - [iav-dataset-show-dataset-dialog-urls]="layer.originDatainfos[0]?.urls" - #kgInfo="iavDatasetShowDatasetDialog"> - </div> - <tile-cmp [tile-image-src]="layer | getPreviewUrlPipe" - class="cursor-pointer pe-all" - tile-image-alt="Preview of this tile" - [tile-text]="layer.displayName || layer.name" - [tile-show-info]="layer.originDatainfos?.length > 0" - [tile-disabled]="!(selectedTemplate$ | async | currentTemplateSupportsParcellation : layer)" - [tile-image-darktheme]="layer.darktheme" - - [tile-selected]="selectedParcellation$ | async | groupParcSelected : layer" - - (tile-on-click)="selectParcellationWithName(layer)" - (tile-on-info-click)="kgInfo && kgInfo.onClick()"> - </tile-cmp> - - </mat-grid-tile> - - <!-- grouped layers --> - <mat-grid-tile *ngFor="let groupKeyVal of (atlasLayersLatest$ | async | getNonbaseParc | getGroupedParc | keyvalue); trackBy: trackKeyVal" - [attr.aria-checked]="false"> - - <!-- prevent click bubbling to document is necessary --> - <!-- or else, the outsideclick directive will fire immediately --> - <!-- resulting in immediate opening and closing of mat menu --> - <tile-cmp [tile-image-src]="groupKeyVal['value'][0] | getPreviewUrlPipe" - class="cursor-pointer pe-all" - tile-image-alt="Preview of this tile" - [tile-text]="groupKeyVal['key']" - [tile-show-info]="false" - [tile-disabled]="!(selectedTemplate$ | async | currentTemplateSupportsParcellation : groupKeyVal['value'])" - [tile-image-darktheme]="groupKeyVal['value'][0].darktheme" - [tile-is-dir]="true" - [matMenuTriggerFor]=layerGroupMenu - [matMenuTriggerData]="{ - layerGroupItems: groupKeyVal['value'] - }" - [tile-selected]="selectedParcellation$ | async | groupParcSelected : groupKeyVal['value']" - iav-stop="click"> - </tile-cmp> - </mat-grid-tile> - </mat-grid-list> - </mat-card-content> - - <div [hidden]="!(showLoadingOverlay$ | async)" - class="loading-overlay"> - <spinner-cmp class="spinner"></spinner-cmp> - </div> -</mat-card> - -<!-- place holder when not expanded --> -<div class="position-relative m-2 cursor-pointer scale-up-bl pe-all" - quick-tour - [quick-tour-description]="quickTourData.description" - [quick-tour-order]="quickTourData.order" - #atlasSelectorTour="quickTour"> - <!-- TODO check when do we disable atlas selector --> - <button color="primary" - *ngIf="true" - matTooltip="Select layer" - mat-mini-fab - [attr.aria-label]="ARIA_LABELS.TOGGLE_ATLAS_LAYER_SELECTOR" - (click)="toggleSelector()"> - <i class="fas fa-layer-group"></i> - </button> -</div> - -<!-- mat menu for grouped layer --> -<mat-menu - #layerGroupMenu="matMenu" - hasBackdrop="false"> - - <ng-template matMenuContent let-layerGroupItems="layerGroupItems"> - <mat-grid-list cols="1" - rowHeight="6:7" - gutterSize="8" - iav-stop="click" - (iav-outsideClick)="collapseExpandedGroup()"> - <mat-grid-tile *ngFor="let layer of layerGroupItems" - [attr.aria-checked]="selectedParcellation$ | async | groupParcSelected : layer"> - - <div [hidden] - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-name]="layer.originDatainfos[0]?.name" - [iav-dataset-show-dataset-dialog-description]="layer.originDatainfos[0]?.description" - [iav-dataset-show-dataset-dialog-urls]="layer.originDatainfos[0]?.urls" - #kgInfo="iavDatasetShowDatasetDialog"> - </div> - - <tile-cmp [tile-image-src]="layer | getPreviewUrlPipe" - class="iv-custom-comp text m-3 cursor-pointer pe-all" - tile-image-alt="Preview of this tile" - [tile-text]="layer.displayName || layer.name" - [tile-show-info]="layer.originDatainfos?.length > 0" - [tile-disabled]="!(selectedTemplate$ | async | currentTemplateSupportsParcellation : layer)" - [tile-image-darktheme]="layer.darktheme" - - [tile-selected]="selectedParcellation$ | async | groupParcSelected : layer" - - (tile-on-click)="selectParcellationWithName(layer)" - (tile-on-info-click)="kgInfo && kgInfo.onClick()"> - </tile-cmp> - - </mat-grid-tile> - </mat-grid-list> - - </ng-template> -</mat-menu> diff --git a/src/atlasComponents/uiSelectors/index.ts b/src/atlasComponents/uiSelectors/index.ts deleted file mode 100644 index 28925e7dac73fcc4f6b8896a1026ec6c556a24e4..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { AtlasCmpUiSelectorsModule } from "./module" -export { AtlasDropdownSelector } from "./atlasDropdown/atlasDropdown.component" -export { AtlasLayerSelector } from "./atlasLayerSelector/atlasLayerSelector.component" \ No newline at end of file diff --git a/src/atlasComponents/uiSelectors/module.ts b/src/atlasComponents/uiSelectors/module.ts deleted file mode 100644 index b6994ea3003f3dbe9815b2986507cd7fdeea20a5..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/module.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { AngularMaterialModule } from "src/sharedModules"; -import { UtilModule } from "src/util"; -import { AtlasDropdownSelector } from "./atlasDropdown/atlasDropdown.component"; -import { AtlasLayerSelector } from "./atlasLayerSelector/atlasLayerSelector.component"; -import {QuickTourModule} from "src/ui/quickTour/module"; -import { KgDatasetModule } from "../regionalFeatures/bsFeatures/kgDataset"; -import { AtlaslayerTooltipPipe } from "./pipes/atlasLayerTooltip.pipe"; -import { ComponentsModule } from "src/components"; -import { GetNonbaseParcPipe } from "./pipes/getNonBaseParc.pipe"; -import { GetIndividualParcPipe } from "./pipes/getIndividualParc.pipe"; -import { GetGroupedParcPipe } from "./pipes/getGroupedParc.pipe"; -import { CurrentTmplSupportsParcPipe } from "./pipes/currTmplSupportsParc.pipe"; -import { GroupParcSelectedPipe } from "./pipes/groupParcSelected.pipe"; -import { GetPreviewUrlPipe } from "./pipes/getPreviewUrl.pipe"; -import { CurrParcSupportsTmplPipe } from "./pipes/currParcSupportsTmpl.pipe"; -import { AtlasCmpParcellationModule } from "../parcellation"; -import { SiibraExplorerTemplateModule } from "../template"; - -@NgModule({ - imports: [ - CommonModule, - AngularMaterialModule, - UtilModule, - QuickTourModule, - KgDatasetModule, - ComponentsModule, - AtlasCmpParcellationModule, - SiibraExplorerTemplateModule, - ], - declarations: [ - AtlasDropdownSelector, - AtlasLayerSelector, - GetPreviewUrlPipe, - AtlaslayerTooltipPipe, - GetNonbaseParcPipe, - GetIndividualParcPipe, - GetGroupedParcPipe, - CurrentTmplSupportsParcPipe, - GroupParcSelectedPipe, - CurrParcSupportsTmplPipe, - ], - exports: [ - AtlasDropdownSelector, - AtlasLayerSelector, - ] -}) - -export class AtlasCmpUiSelectorsModule{} diff --git a/src/atlasComponents/uiSelectors/pipes/atlasLayerTooltip.pipe.ts b/src/atlasComponents/uiSelectors/pipes/atlasLayerTooltip.pipe.ts deleted file mode 100644 index 285f134543afa5151de4627fe2f507515e5f81de..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/pipes/atlasLayerTooltip.pipe.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name: 'atlasLayerTooltip', - pure: true -}) - -export class AtlaslayerTooltipPipe implements PipeTransform{ - public transform(layer: any){ - return layer.name - } -} diff --git a/src/atlasComponents/uiSelectors/pipes/currParcSupportsTmpl.pipe.ts b/src/atlasComponents/uiSelectors/pipes/currParcSupportsTmpl.pipe.ts deleted file mode 100644 index dcdf13d404ba747a5ce65f9f1bbe6fb08189b196..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/pipes/currParcSupportsTmpl.pipe.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name: 'currParcSupportsTmpl', - pure: true -}) - -export class CurrParcSupportsTmplPipe implements PipeTransform{ - public transform(parc: any, tmpl: any){ - /** - * TODO - * buggy. says julich brain v290 is not supported in fsaverage - * related to https://github.com/FZJ-INM1-BDA/siibra-python/issues/98 - */ - const parcSupportTmpl = (p: any) => !!(tmpl.availableIn || []).find(tmplP => tmplP['@id'] === p && p['@id']) - return Array.isArray(parc) - ? parc.some(parcSupportTmpl) - : parcSupportTmpl(parc) - } -} diff --git a/src/atlasComponents/uiSelectors/pipes/currTmplSupportsParc.pipe.ts b/src/atlasComponents/uiSelectors/pipes/currTmplSupportsParc.pipe.ts deleted file mode 100644 index 6d1ed06bd92acea21148e64bc6815c5d4345aa73..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/pipes/currTmplSupportsParc.pipe.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name: 'currentTemplateSupportsParcellation', - pure: true -}) - -export class CurrentTmplSupportsParcPipe implements PipeTransform{ - public transform(tmpl: any, parc: any): boolean { - const testParc = (p: any) => !!(p?.availableIn || []).find((availTmpl: any) => availTmpl['@id'] === tmpl['@id']) - return Array.isArray(parc) - ? parc.some(testParc) - : testParc(parc) - } -} diff --git a/src/atlasComponents/uiSelectors/pipes/getGroupedParc.pipe.ts b/src/atlasComponents/uiSelectors/pipes/getGroupedParc.pipe.ts deleted file mode 100644 index cd98f91a417520f57af7f9ae9952ffd134716a8f..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/pipes/getGroupedParc.pipe.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -type TReturn = { - [key: string]: any[] -} - -@Pipe({ - name: 'getGroupedParc', - pure: true -}) -export class GetGroupedParcPipe implements PipeTransform{ - - public transform(arr: any[]):TReturn{ - const returnObj: TReturn = {} - const filteredArr = arr.filter(p => p['groupName']) - for (const obj of filteredArr) { - const groupName: string = obj['groupName'] - returnObj[groupName] = (returnObj[groupName] || []).concat(obj) - } - return returnObj - } -} diff --git a/src/atlasComponents/uiSelectors/pipes/getIndividualParc.pipe.ts b/src/atlasComponents/uiSelectors/pipes/getIndividualParc.pipe.ts deleted file mode 100644 index 1e7b155b258819a4490ed41083aa0f55f6051ee4..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/pipes/getIndividualParc.pipe.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name: 'getIndividualParc', - pure: true -}) -export class GetIndividualParcPipe implements PipeTransform{ - - public transform(arr: any[]){ - return arr.filter(p => !p['groupName']) - } -} diff --git a/src/atlasComponents/uiSelectors/pipes/getNonBaseParc.pipe.ts b/src/atlasComponents/uiSelectors/pipes/getNonBaseParc.pipe.ts deleted file mode 100644 index e0b7997bfa191ee6e790fc935a541e7c8d9a5bb1..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/pipes/getNonBaseParc.pipe.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name: 'getNonbaseParc', - pure: true -}) -export class GetNonbaseParcPipe implements PipeTransform{ - - public transform(arr: any[]){ - return arr.filter(p => !p['baseLayer']) - } -} diff --git a/src/atlasComponents/uiSelectors/pipes/getPreviewUrl.pipe.ts b/src/atlasComponents/uiSelectors/pipes/getPreviewUrl.pipe.ts deleted file mode 100644 index 7826f44e267119a0cab9c848f0dd368280629b95..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/pipes/getPreviewUrl.pipe.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { GetParcPreviewUrlPipe } from "src/atlasComponents/parcellation"; -import { GetTemplatePreviewUrlPipe } from "src/atlasComponents/template"; - -const templateUrlPipe = new GetTemplatePreviewUrlPipe() -const parcUrlPipe = new GetParcPreviewUrlPipe() - -@Pipe({ - name: 'getPreviewUrlPipe', - pure: true -}) - -export class GetPreviewUrlPipe implements PipeTransform{ - public transform(tile: any){ - const filename = templateUrlPipe.transform(tile) || parcUrlPipe.transform(tile) - return filename - } -} diff --git a/src/atlasComponents/uiSelectors/pipes/groupParcSelected.pipe.ts b/src/atlasComponents/uiSelectors/pipes/groupParcSelected.pipe.ts deleted file mode 100644 index 6beb302be626272f72e62f23b722ce1ce7411015..0000000000000000000000000000000000000000 --- a/src/atlasComponents/uiSelectors/pipes/groupParcSelected.pipe.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name: 'groupParcSelected', - pure: true -}) - -export class GroupParcSelectedPipe implements PipeTransform{ - public transform(selectedParc: any, parc: any){ - const isSelected = (p: any) => p['@id'] === selectedParc['@id'] - return Array.isArray(parc) - ? parc.some(isSelected) - : isSelected(parc) - } -} diff --git a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html index f0832efe03d55a7295ad5699d058d35d029304de..d90110fa3f66f2a6483787462e5432bc4704986f 100644 --- a/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html +++ b/src/atlasComponents/userAnnotations/annotationList/annotationList.template.html @@ -55,7 +55,7 @@ </div> <!-- content --> - <mat-card-content class="mt-4 ml-15px-n mr-15px-n pb-4"> + <mat-card-content class="mt-4 ml-15px-n mr-15px-n sxplr-pb-4"> <!-- list of annotations --> <ng-template [ngIf]="managedAnnotations$ | async" [ngIfElse]="placeholderTmpl" let-managedAnnotations> @@ -98,14 +98,14 @@ </ng-template> <ng-template [ngIf]="annotationInOtherSpaces$ | async" let-annsInOtherSpace> - <div *ngIf="annsInOtherSpace.length > 0" class="p-4 text-muted"> + <div *ngIf="annsInOtherSpace.length > 0" class="sxplr-p-4 text-muted"> {{ annsInOtherSpace.length }} annotations found in other reference spaces, and not shown here. </div> </ng-template> <!-- place holder when no annotations exist --> <ng-template #placeholderTmpl> - <div class="p-4 text-muted"> + <div class="sxplr-p-4 text-muted"> <p>Start by adding an annotation, or import existing annotations.</p> </div> </ng-template> diff --git a/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.component.ts b/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.component.ts index 8c9a11566b9f7f27350a684cc9ffc3976525c32e..327439092987a0dae77a60d0d409f5200f6b60c6 100644 --- a/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.component.ts +++ b/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.component.ts @@ -5,10 +5,11 @@ import { Polygon } from '../tools/poly' import { FormControl, FormGroup } from "@angular/forms"; import { Subscription } from "rxjs"; import { select, Store } from "@ngrx/store"; -import { viewerStateFetchedAtlasesSelector } from "src/services/state/viewerState/selectors"; import { ModularUserAnnotationToolService } from "../tools/service"; import { MatSnackBar } from "@angular/material/snack-bar"; import { Line } from "../tools/line"; +import { atlasSelection } from "src/state"; +import { map } from "rxjs/operators"; @Component({ selector: 'single-annotation-unit', @@ -31,7 +32,6 @@ export class SingleAnnotationUnit implements OnDestroy, AfterViewInit{ private subs: Subscription[] = [] public templateSpaces: { ['@id']: string - name: string }[] = [] ngOnChanges(){ while(this.chSubs.length > 0) this.chSubs.pop().unsubscribe() @@ -52,30 +52,22 @@ export class SingleAnnotationUnit implements OnDestroy, AfterViewInit{ this.managedAnnotation.desc = desc }) ) - } + public tmpls$ = this.store.pipe( + select(atlasSelection.selectors.selectedTemplate), + map(val => { + return [val] + }) + ) + constructor( - store: Store<any>, + private store: Store<any>, private snackbar: MatSnackBar, private svc: ModularUserAnnotationToolService, private cfr: ComponentFactoryResolver, private injector: Injector, ){ - this.subs.push( - store.pipe( - select(viewerStateFetchedAtlasesSelector), - ).subscribe(atlases => { - for (const atlas of atlases) { - for (const tmpl of atlas.templateSpaces) { - this.templateSpaces.push({ - '@id': tmpl['@id'], - name: tmpl.name - }) - } - } - }) - ) } ngAfterViewInit(){ diff --git a/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.template.html b/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.template.html index e03b3068ebc2360cc9e6a08c537c73c750c4ece9..51f1c004700f834b92c3afd81d6cd93ee3d01026 100644 --- a/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.template.html +++ b/src/atlasComponents/userAnnotations/singleAnnotationUnit/singleAnnotationUnit.template.html @@ -4,8 +4,8 @@ Space </mat-label> <mat-select formControlName="spaceId"> - <mat-option *ngFor="let tmpl of templateSpaces" [value]="tmpl['@id']"> - {{ tmpl.name }} + <mat-option *ngFor="let tmpl of tmpls$ | async" [value]="tmpl['@id']"> + {{ tmpl.fullName }} </mat-option> </mat-select> </mat-form-field> diff --git a/src/atlasComponents/userAnnotations/tools/line/line.component.ts b/src/atlasComponents/userAnnotations/tools/line/line.component.ts index bf3d32401fc9cfc2378b2a4d17000e53cef2390d..a98e126c64223483c2bbaf9131f9db091d3cb2f2 100644 --- a/src/atlasComponents/userAnnotations/tools/line/line.component.ts +++ b/src/atlasComponents/userAnnotations/tools/line/line.component.ts @@ -4,10 +4,10 @@ import { Store } from "@ngrx/store"; import { Line, LINE_ICON_CLASS } from "../line"; import { ToolCmpBase } from "../toolCmp.base"; import { IAnnotationGeometry, TExportFormats, UDPATE_ANNOTATION_TOKEN } from "../type"; -import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions"; import { Point } from "../point"; import { ARIA_LABELS } from 'common/constants' import { ComponentStore } from "src/viewerModule/componentStore"; +import { actions } from "src/state/atlasSelection"; @Component({ selector: 'line-update-cmp', @@ -59,12 +59,12 @@ export class LineUpdateCmp extends ToolCmpBase implements OnDestroy{ const { x, y, z } = roi this.store.dispatch( - viewerStateChangeNavigation({ + actions.navigateTo({ navigation: { - position: [x, y, z], - positionReal: true, - animation: {} - } + position: [x, y, z] + }, + physical: true, + animation: true }) ) return @@ -79,12 +79,12 @@ export class LineUpdateCmp extends ToolCmpBase implements OnDestroy{ const { x, y, z } = this.updateAnnotation.points[0] this.store.dispatch( - viewerStateChangeNavigation({ + actions.navigateTo({ navigation: { - position: [x, y, z], - positionReal: true, - animation: {} - } + position: [x, y, z] + }, + physical: true, + animation: true }) ) } diff --git a/src/atlasComponents/userAnnotations/tools/line/line.template.html b/src/atlasComponents/userAnnotations/tools/line/line.template.html index e0b3b06a78c4ebb58e26e4d9254f6c59e74a2ca3..3f72bd1d7a590f34b3f20636a44a7ff46f9ce2e2 100644 --- a/src/atlasComponents/userAnnotations/tools/line/line.template.html +++ b/src/atlasComponents/userAnnotations/tools/line/line.template.html @@ -36,10 +36,10 @@ </div> <mat-menu #exportMenu="matMenu" xPosition="before"> - <div class="iv-custom-comp card text" + <div class="sxplr-custom-cmp card text" iav-stop="click"> - <div class="iv-custom-comp text"> + <div class="sxplr-custom-cmp text"> <textarea-copy-export [textarea-copy-export-label]="useFormat" [textarea-copy-export-text]="updateAnnotation.updateSignal$ | async | toFormattedStringPipe : updateAnnotation : useFormat" diff --git a/src/atlasComponents/userAnnotations/tools/point/point.component.ts b/src/atlasComponents/userAnnotations/tools/point/point.component.ts index c28152125a1a006ac1f69d8543792364dcb8b4ab..b4ef51006975e34cdcc753ad85cf1dc10056a9dd 100644 --- a/src/atlasComponents/userAnnotations/tools/point/point.component.ts +++ b/src/atlasComponents/userAnnotations/tools/point/point.component.ts @@ -3,9 +3,9 @@ import { Point, POINT_ICON_CLASS } from "../point"; import { IAnnotationGeometry, TExportFormats, UDPATE_ANNOTATION_TOKEN } from "../type"; import { ToolCmpBase } from "../toolCmp.base"; import { Store } from "@ngrx/store"; -import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions"; import { ComponentStore } from "src/viewerModule/componentStore"; import { ARIA_LABELS } from 'common/constants' +import { actions } from "src/state/atlasSelection"; @Component({ selector: 'point-update-cmp', @@ -55,12 +55,12 @@ export class PointUpdateCmp extends ToolCmpBase implements OnDestroy{ } const { x, y, z } = this.updateAnnotation this.store.dispatch( - viewerStateChangeNavigation({ + actions.navigateTo({ navigation: { - position: [x, y, z], - positionReal: true, - animation: {} - } + position: [x, y, z] + }, + physical: true, + animation: true }) ) } diff --git a/src/atlasComponents/userAnnotations/tools/point/point.template.html b/src/atlasComponents/userAnnotations/tools/point/point.template.html index b1b5ab285e11bc2926f3a00d4b8b0b39b4ab0579..805ab08767a59c9f202ae053b814150678bef881 100644 --- a/src/atlasComponents/userAnnotations/tools/point/point.template.html +++ b/src/atlasComponents/userAnnotations/tools/point/point.template.html @@ -35,10 +35,10 @@ </div> <mat-menu #exportMenu="matMenu" xPosition="before"> - <div class="iv-custom-comp card text" + <div class="sxplr-custom-cmp card text" iav-stop="click"> - <div class="iv-custom-comp text"> + <div class="sxplr-custom-cmp text"> <textarea-copy-export [textarea-copy-export-label]="useFormat" diff --git a/src/atlasComponents/userAnnotations/tools/poly/poly.component.ts b/src/atlasComponents/userAnnotations/tools/poly/poly.component.ts index 9d8371b404a5eec8d4bc4ca60bbcf07cdfac2c5e..6e8852d88b665d2b988ed59f1340c311d0086515 100644 --- a/src/atlasComponents/userAnnotations/tools/poly/poly.component.ts +++ b/src/atlasComponents/userAnnotations/tools/poly/poly.component.ts @@ -3,11 +3,11 @@ import { MatSnackBar } from "@angular/material/snack-bar"; import { Polygon, POLY_ICON_CLASS } from "../poly"; import { ToolCmpBase } from "../toolCmp.base"; import { IAnnotationGeometry, TExportFormats, UDPATE_ANNOTATION_TOKEN } from "../type"; -import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions"; import { Store } from "@ngrx/store"; import { Point } from "../point"; import { ARIA_LABELS } from 'common/constants' import { ComponentStore } from "src/viewerModule/componentStore"; +import { actions } from "src/state/atlasSelection"; @Component({ selector: 'poly-update-cmp', @@ -68,12 +68,12 @@ export class PolyUpdateCmp extends ToolCmpBase implements OnDestroy{ const { x, y, z } = roi this.store.dispatch( - viewerStateChangeNavigation({ + actions.navigateTo({ navigation: { - position: [x, y, z], - positionReal: true, - animation: {} - } + position: [x, y, z] + }, + physical: true, + animation: true }) ) return @@ -88,12 +88,12 @@ export class PolyUpdateCmp extends ToolCmpBase implements OnDestroy{ const { x, y, z } = this.updateAnnotation.points[0] this.store.dispatch( - viewerStateChangeNavigation({ + actions.navigateTo({ navigation: { - position: [x, y, z], - positionReal: true, - animation: {} - } + position: [x, y, z] + }, + physical: true, + animation: true }) ) } diff --git a/src/atlasComponents/userAnnotations/tools/poly/poly.template.html b/src/atlasComponents/userAnnotations/tools/poly/poly.template.html index ea8665437974b0c1d29b31de294b72ed426f7095..6827915b80ecfb04504184c6519b9670cfa62cf0 100644 --- a/src/atlasComponents/userAnnotations/tools/poly/poly.template.html +++ b/src/atlasComponents/userAnnotations/tools/poly/poly.template.html @@ -36,10 +36,10 @@ </div> <mat-menu #exportMenu="matMenu" xPosition="before"> - <div class="iv-custom-comp card text" + <div class="sxplr-custom-cmp card text" iav-stop="click"> - <div class="iv-custom-comp text"> + <div class="sxplr-custom-cmp text"> <textarea-copy-export [textarea-copy-export-label]="useFormat" [textarea-copy-export-text]="updateAnnotation.updateSignal$ | async | toFormattedStringPipe : updateAnnotation : useFormat" diff --git a/src/atlasComponents/userAnnotations/tools/service.ts b/src/atlasComponents/userAnnotations/tools/service.ts index 26fc809549b7f7fd59740539aaaec0e09ae66a4a..e998c9c53a65be4965b8cad62e84d1b45df3c3f3 100644 --- a/src/atlasComponents/userAnnotations/tools/service.ts +++ b/src/atlasComponents/userAnnotations/tools/service.ts @@ -4,18 +4,20 @@ import { Inject, Optional } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { BehaviorSubject, combineLatest, fromEvent, merge, Observable, of, Subject, Subscription } from "rxjs"; import {map, switchMap, filter, shareReplay, pairwise } from "rxjs/operators"; -import { viewerStateSelectedTemplatePureSelector, viewerStateViewerModeSelector } from "src/services/state/viewerState/selectors"; import { NehubaViewerUnit } from "src/viewerModule/nehuba"; import { NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba/util"; import { AbsToolClass, ANNOTATION_EVENT_INJ_TOKEN, IAnnotationEvents, IAnnotationGeometry, INgAnnotationTypes, INJ_ANNOT_TARGET, TAnnotationEvent, ClassInterface, TCallbackFunction, TSands, TGeometryJson, TNgAnnotationLine, TCallback } from "./type"; -import { switchMapWaitFor } from "src/util/fn"; +import { getExportNehuba, switchMapWaitFor } from "src/util/fn"; import { Polygon } from "./poly"; import { Line } from "./line"; import { Point } from "./point"; import { FilterAnnotationsBySpace } from "../filterAnnotationBySpace.pipe"; import { retry } from 'common/util' import { MatSnackBar } from "@angular/material/snack-bar"; -import { viewerStateSetViewerMode } from "src/services/state/viewerState.store.helper"; +import { actions } from "src/state/atlasSelection"; +import { atlasSelection } from "src/state"; +import { SapiSpaceModel } from "src/atlasComponents/sapi"; +import { AnnotationLayer } from "src/atlasComponents/annotations"; const LOCAL_STORAGE_KEY = 'userAnnotationKey' @@ -85,13 +87,13 @@ export class ModularUserAnnotationToolService implements OnDestroy{ private previewNgAnnIds: string[] = [] - private ngAnnotationLayer: any + private annotationLayer: AnnotationLayer private activeToolName: string private forcedAnnotationRefresh$ = new BehaviorSubject(null) - private selectedTmpl: any + private selectedTmpl: SapiSpaceModel private selectedTmpl$ = this.store.pipe( - select(viewerStateSelectedTemplatePureSelector), + select(atlasSelection.selectors.selectedTemplate), ) public moduleAnnotationTypes: {instance: {name: string, iconClass: string, toolSelected$: Observable<boolean>}, onClick: () => void}[] = [] private managedAnnotationsStream$ = new Subject<{ @@ -321,36 +323,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{ */ this.subscription.push( nehubaViewer$.subscribe(() => { - this.ngAnnotationLayer = null - }) - ) - - /** - * on new nehubaViewer, listen to mouseState - */ - let cb: () => void - this.subscription.push( - nehubaViewer$.pipe( - switchMap(switchMapWaitFor({ - condition: nv => !!(nv?.nehubaViewer), - })) - ).subscribe(nehubaViewer => { - if (cb) cb() - if (nehubaViewer) { - const mouseState = nehubaViewer.nehubaViewer.ngviewer.mouseState - cb = mouseState.changed.add(() => { - const payload: IAnnotationEvents['hoverAnnotation'] = mouseState.active && !!mouseState.pickedAnnotationId - ? { - pickedAnnotationId: mouseState.pickedAnnotationId, - pickedOffset: mouseState.pickedOffset - } - : null - this.annotnEvSubj.next({ - type: 'hoverAnnotation', - detail: payload - }) - }) - } + this.annotationLayer = null }) ) @@ -411,23 +384,12 @@ export class ModularUserAnnotationToolService implements OnDestroy{ this.clearAllPreviewAnnotations() } for (let idx = 0; idx < previewNgAnnotation.length; idx ++) { - const localAnnotations = this.ngAnnotationLayer.layer.localAnnotations - const annSpec = { - ...parseNgAnnotation(previewNgAnnotation[idx]), + const spec = { + ...previewNgAnnotation[idx], id: `${ModularUserAnnotationToolService.TMP_PREVIEW_ANN_ID}_${idx}` } - const annRef = localAnnotations.references.get(annSpec.id) - if (annRef) { - localAnnotations.update( - annRef, - annSpec - ) - } else { - localAnnotations.add( - annSpec - ) - } - this.previewNgAnnIds[idx] = annSpec.id + this.annotationLayer.updateAnnotation(spec) + this.previewNgAnnIds[idx] = spec.id } }) ) @@ -439,7 +401,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{ this.forcedAnnotationRefresh$, this.spaceFilteredManagedAnnotations$.pipe( switchMap(switchMapWaitFor({ - condition: () => !!this.ngAnnotationLayer, + condition: () => !!this.annotationLayer, leading: true })), ) @@ -479,20 +441,8 @@ export class ModularUserAnnotationToolService implements OnDestroy{ this.deleteNgAnnotationById(annotation.id) continue } - if (!this.ngAnnotationLayer) continue - const localAnnotations = this.ngAnnotationLayer.layer.localAnnotations - const annRef = localAnnotations.references.get(annotation.id) - const annSpec = parseNgAnnotation(annotation) - if (annRef) { - localAnnotations.update( - annRef, - annSpec - ) - } else { - localAnnotations.add( - annSpec - ) - } + if (!this.annotationLayer) continue + this.annotationLayer.updateAnnotation(annotation) } }) ) @@ -502,30 +452,33 @@ export class ModularUserAnnotationToolService implements OnDestroy{ */ this.subscription.push( store.pipe( - select(viewerStateViewerModeSelector) + select(atlasSelection.selectors.viewerMode) ).subscribe(viewerMode => { this.currMode = viewerMode if (viewerMode === ModularUserAnnotationToolService.VIEWER_MODE) { - if (this.ngAnnotationLayer) this.ngAnnotationLayer.setVisible(true) + if (this.annotationLayer) this.annotationLayer.setVisible(true) else { const viewer = (window as any).viewer - const voxelSize = IAV_VOXEL_SIZES_NM[this.selectedTmpl.fullId] - if (!voxelSize) throw new Error(`voxelSize of ${this.selectedTmpl.fullId} cannot be found!`) - const layer = viewer.layerSpecification.getLayer( + const voxelSize = IAV_VOXEL_SIZES_NM[this.selectedTmpl["@id"]] + if (!voxelSize) throw new Error(`voxelSize of ${this.selectedTmpl["@id"]} cannot be found!`) + if (this.annotationLayer) { + this.annotationLayer.dispose() + } + this.annotationLayer = new AnnotationLayer( ModularUserAnnotationToolService.ANNOTATION_LAYER_NAME, - { - ...ModularUserAnnotationToolService.USER_ANNOTATION_LAYER_SPEC, - // since voxel coordinates are no longer defined, so voxel size will always be 1/1/1 - transform: [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - ] - } + ModularUserAnnotationToolService.USER_ANNOTATION_LAYER_SPEC.annotationColor ) - this.ngAnnotationLayer = viewer.layerManager.addManagedLayer(layer) - + this.annotationLayer.onHover.subscribe(val => { + this.annotnEvSubj.next({ + type: 'hoverAnnotation', + detail: val + ? { + pickedAnnotationId: val.id, + pickedOffset: val.offset + } + : null + }) + }) /** * on template changes, the layer gets lost * force redraw annotations if layer needs to be recreated @@ -533,7 +486,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{ this.forcedAnnotationRefresh$.next(null) } } else { - if (this.ngAnnotationLayer) this.ngAnnotationLayer.setVisible(false) + if (this.annotationLayer) this.annotationLayer.setVisible(false) } }) ) @@ -583,14 +536,14 @@ export class ModularUserAnnotationToolService implements OnDestroy{ const bin = atob(encoded) await retry(() => { - if (!!(window as any).export_nehuba) return true + if (!!getExportNehuba()) return true else throw new Error(`export nehuba not yet ready`) }, { timeout: 1000, retries: 10 }) - const { pako } = (window as any).export_nehuba + const { pako } = getExportNehuba() const decoded = pako.inflate(bin, { to: 'string' }) const arr = JSON.parse(decoded) const anns: IAnnotationGeometry[] = [] @@ -626,8 +579,9 @@ export class ModularUserAnnotationToolService implements OnDestroy{ arr.push(json) } const stringifiedJSON = JSON.stringify(arr) - if (!(window as any).export_nehuba) return - const { pako } = (window as any).export_nehuba + const exportNehuba = getExportNehuba() + if (!exportNehuba) return + const { pako } = exportNehuba const compressed = pako.deflate(stringifiedJSON) let out = '' for (const num of compressed) { @@ -666,12 +620,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{ } private deleteNgAnnotationById(annId: string) { - const localAnnotations = this.ngAnnotationLayer.layer.localAnnotations - const annRef = localAnnotations.references.get(annId) - if (annRef) { - localAnnotations.delete(annRef) - localAnnotations.references.delete(annId) - } + this.annotationLayer.removeAnnotation({ id: annId }) } public defaultTool: AbsToolClass<any> @@ -749,7 +698,7 @@ export class ModularUserAnnotationToolService implements OnDestroy{ private currMode: string switchAnnotationMode(mode: 'on' | 'off' | 'toggle' = 'toggle') { - let payload = null + let payload: 'annotating' = null if (mode === 'on') payload = ARIA_LABELS.VIEWER_MODE_ANNOTATING if (mode === 'off') { if (this.currMode === ARIA_LABELS.VIEWER_MODE_ANNOTATING) payload = null @@ -761,7 +710,9 @@ export class ModularUserAnnotationToolService implements OnDestroy{ : ARIA_LABELS.VIEWER_MODE_ANNOTATING } this.store.dispatch( - viewerStateSetViewerMode({ payload }) + actions.setViewerMode({ + viewerMode: payload + }) ) } } diff --git a/src/atlasViewer/atlasViewer.apiService.service.spec.ts b/src/atlasViewer/atlasViewer.apiService.service.spec.ts deleted file mode 100644 index 2f510f5d98763e61d25f46a9ac157f9d19308cea..0000000000000000000000000000000000000000 --- a/src/atlasViewer/atlasViewer.apiService.service.spec.ts +++ /dev/null @@ -1,279 +0,0 @@ -import { AtlasViewerAPIServices } from "src/atlasViewer/atlasViewer.apiService.service"; -import { async, TestBed, fakeAsync, tick } from "@angular/core/testing"; -import { provideMockStore } from "@ngrx/store/testing"; -import { defaultRootState } from "src/services/stateStore.service"; -import { AngularMaterialModule } from "src/sharedModules"; -import { WidgetModule } from 'src/widget'; -import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing"; -import { PluginServices } from "src/plugin"; -import { CANCELLABLE_DIALOG } from "src/util/interfaces"; - -describe('atlasViewer.apiService.service.ts', () => { - - describe('AtlasViewerAPIServices', () => { - - const cancelTokenSpy = jasmine.createSpy('cancelToken') - const cancellableDialogSpy = jasmine.createSpy('openCallableDialog').and.returnValue(cancelTokenSpy) - - afterEach(() => { - cancelTokenSpy.calls.reset() - cancellableDialogSpy.calls.reset() - - const ctrl = TestBed.inject(HttpTestingController) - ctrl.verify() - }) - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - AngularMaterialModule, - HttpClientTestingModule, - WidgetModule, - ], - providers: [ - AtlasViewerAPIServices, - provideMockStore({ initialState: defaultRootState }), - { - provide: CANCELLABLE_DIALOG, - useValue: cancellableDialogSpy - }, - { - provide: PluginServices, - useValue: {} - } - ] - }).compileComponents() - })) - - it('service exists', () => { - const service = TestBed.inject(AtlasViewerAPIServices) - expect(service).not.toBeNull() - }) - - describe('uiHandle', () => { - - describe('getUserToSelectARegion', () => { - - it('on init, expect getUserToSelectRegion to be length 0', () => { - const service = TestBed.inject(AtlasViewerAPIServices) - expect(service.getUserToSelectRegion.length).toEqual(0) - }) - it('calling getUserToSelectARegion() populates getUserToSelectRegion', () => { - const service = TestBed.inject(AtlasViewerAPIServices) - - const pr = service.interactiveViewer.uiHandle.getUserToSelectARegion('hello world') - - expect(service.getUserToSelectRegion.length).toEqual(1) - const { promise, message, rs, rj } = service.getUserToSelectRegion[0] - expect(promise).toEqual(pr) - expect(message).toEqual('hello world') - - expect(rs).not.toBeUndefined() - expect(rs).not.toBeNull() - - expect(rj).not.toBeUndefined() - expect(rj).not.toBeNull() - }) - }) - - describe('> getUserToSelectRoi', () => { - it('> calling getUserToSelectRoi without spec throws error', () => { - const service = TestBed.inject(AtlasViewerAPIServices) - expect(() => { - service.interactiveViewer.uiHandle.getUserToSelectRoi('hello world') - }).toThrow() - }) - - it('> calling getUserToSelectRoi without spec.type throws', () => { - const service = TestBed.inject(AtlasViewerAPIServices) - expect(() => { - service.interactiveViewer.uiHandle.getUserToSelectRoi('hello world', { foo: 'bar' } as any) - }).toThrow() - }) - - it('> calling getUserToSelectRoi populates getUserToSelectRegion with malformed spec.type is fine', () => { - const service = TestBed.inject(AtlasViewerAPIServices) - expect(() => { - service.interactiveViewer.uiHandle.getUserToSelectRoi('hello world', { type: 'foobar' }) - }).not.toThrow() - }) - it('> calling getUserToSelectRoi populates getUserToSelectRegion', () => { - - const service = TestBed.inject(AtlasViewerAPIServices) - - const pr = service.interactiveViewer.uiHandle.getUserToSelectRoi('hello world', { type: 'POINT' }) - - expect(service.getUserToSelectRegion.length).toEqual(1) - const { promise, message, spec, rs, rj } = service.getUserToSelectRegion[0] - expect(promise).toEqual(pr) - expect(message).toEqual('hello world') - expect(spec).toEqual({ type: 'POINT' }) - - expect(rs).not.toBeFalsy() - expect(rj).not.toBeFalsy() - }) - }) - - describe('cancelPromise', () => { - it('calling cancelPromise removes pr from getUsertoSelectRegion', done => { - - const service = TestBed.inject(AtlasViewerAPIServices) - const pr = service.interactiveViewer.uiHandle.getUserToSelectARegion('test') - pr.catch(e => { - expect(e.userInitiated).toEqual(false) - expect(service.getUserToSelectRegion.length).toEqual(0) - done() - }) - service.interactiveViewer.uiHandle.cancelPromise(pr) - }) - - it('alling cancelPromise on non existing promise, throws ', () => { - - const service = TestBed.inject(AtlasViewerAPIServices) - const pr = service.interactiveViewer.uiHandle.getUserToSelectARegion('test') - service.interactiveViewer.uiHandle.cancelPromise(pr) - expect(() => { - service.interactiveViewer.uiHandle.cancelPromise(pr) - }).toThrow() - }) - }) - - describe('getUserToSelectARegion, cancelPromise and userCancel', () => { - it('if token is provided, on getUserToSelectRegionUI$ next should follow by call to injected function', () => { - const service = TestBed.inject(AtlasViewerAPIServices) - - const rsSpy = jasmine.createSpy('rs') - const rjSpy = jasmine.createSpy('rj') - const mockObj = { - message: 'test', - promise: new Promise((rs, rj) => {}), - rs: rsSpy, - rj: rjSpy, - } - service.getUserToSelectRegionUI$.next([ mockObj ]) - - - expect(cancellableDialogSpy).toHaveBeenCalled() - - const arg = cancellableDialogSpy.calls.mostRecent().args - expect(arg[0]).toEqual('test') - expect(arg[1].userCancelCallback).toBeTruthy() - }) - - it('if multiple regionUIs are provided, only the last one is used', () => { - const service = TestBed.inject(AtlasViewerAPIServices) - - const rsSpy = jasmine.createSpy('rs') - const rjSpy = jasmine.createSpy('rj') - const mockObj1 = { - message: 'test1', - promise: new Promise((rs, rj) => {}), - rs: rsSpy, - rj: rjSpy, - } - const mockObj2 = { - message: 'test2', - promise: new Promise((rs, rj) => {}), - rs: rsSpy, - rj: rjSpy, - } - service.getUserToSelectRegionUI$.next([ mockObj1, mockObj2 ]) - - expect(cancellableDialogSpy).toHaveBeenCalled() - - const arg = cancellableDialogSpy.calls.mostRecent().args - expect(arg[0]).toEqual('test2') - expect(arg[1].userCancelCallback).toBeTruthy() - }) - - describe('calling userCacellationCb', () => { - - it('correct usage => in removeBasedOnPr called, rj with userini as true', fakeAsync(() => { - const service = TestBed.inject(AtlasViewerAPIServices) - - const rsSpy = jasmine.createSpy('rs') - const rjSpy = jasmine.createSpy('rj') - const promise = new Promise((rs, rj) => {}) - const mockObj = { - message: 'test', - promise, - rs: rsSpy, - rj: rjSpy, - } - - const removeBaseOnPr = spyOn(service, 'removeBasedOnPr').and.returnValue(null) - - service.getUserToSelectRegionUI$.next([ mockObj ]) - const arg = cancellableDialogSpy.calls.mostRecent().args - const cb = arg[1].userCancelCallback - cb() - tick(100) - expect(rjSpy).toHaveBeenCalledWith({ userInitiated: true }) - expect(removeBaseOnPr).toHaveBeenCalledWith(promise, { userInitiated: true }) - - })) - - it('incorrect usage (resolve) => removebasedonpr, rj not called', fakeAsync(() => { - - const service = TestBed.inject(AtlasViewerAPIServices) - - const dummyObj = { - hello:'world' - } - - const rsSpy = jasmine.createSpy('rs') - const rjSpy = jasmine.createSpy('rj') - const promise = Promise.resolve(dummyObj) - const mockObj = { - message: 'test', - promise, - rs: rsSpy, - rj: rjSpy, - } - - const removeBaseOnPr = spyOn(service, 'removeBasedOnPr').and.returnValue(null) - - service.getUserToSelectRegionUI$.next([ mockObj ]) - const arg = cancellableDialogSpy.calls.mostRecent().args - const cb = arg[1].userCancelCallback - cb() - tick(100) - expect(rjSpy).not.toHaveBeenCalled() - expect(removeBaseOnPr).not.toHaveBeenCalled() - - })) - - it('incorrect usage (reject) => removebasedonpr, rj not called', fakeAsync(() => { - - const service = TestBed.inject(AtlasViewerAPIServices) - - const dummyObj = { - hello:'world' - } - - const rsSpy = jasmine.createSpy('rs') - const rjSpy = jasmine.createSpy('rj') - const promise = Promise.reject(dummyObj) - const mockObj = { - message: 'test', - promise, - rs: rsSpy, - rj: rjSpy, - } - - const removeBaseOnPr = spyOn(service, 'removeBasedOnPr').and.returnValue(null) - - service.getUserToSelectRegionUI$.next([ mockObj ]) - const arg = cancellableDialogSpy.calls.mostRecent().args - const cb = arg[1].userCancelCallback - cb() - tick(100) - expect(rjSpy).not.toHaveBeenCalled() - expect(removeBaseOnPr).not.toHaveBeenCalled() - - })) - }) - }) - }) - }) -}) diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts deleted file mode 100644 index ad6d2fb7e7057784edadb8b227f015f4e83f74ac..0000000000000000000000000000000000000000 --- a/src/atlasViewer/atlasViewer.apiService.service.ts +++ /dev/null @@ -1,468 +0,0 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import {Injectable, NgZone, Optional, Inject, OnDestroy, InjectionToken} from "@angular/core"; -import { MatSnackBar } from "@angular/material/snack-bar"; -import { select, Store } from "@ngrx/store"; -import { Observable, Subject, Subscription, from, race, of, } from "rxjs"; -import { distinctUntilChanged, map, filter, startWith, switchMap, catchError, mapTo, take } from "rxjs/operators"; -import { DialogService } from "src/services/dialogService.service"; -import { uiStateMouseOverSegmentsSelector } from "src/services/state/uiState/selectors"; -import { - viewerStateFetchedTemplatesSelector, -} from "src/services/state/viewerState/selectors"; -import { - getLabelIndexMap, - getMultiNgIdsRegionsLabelIndexMap, - IavRootStoreInterface, - safeFilter -} from "src/services/stateStore.service"; -import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; -import { FRAGMENT_EMIT_RED } from "src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component"; -import { IPluginManifest, PluginServices } from "src/plugin"; -import { ILoadMesh } from 'src/messaging/types' -import { CANCELLABLE_DIALOG } from "src/util/interfaces"; - -declare let window - -interface IRejectUserInput{ - userInitiated: boolean - reason?: string -} - -interface IGetUserSelectRegionPr{ - message: string - promise: Promise<any> - spec?: ICustomRegionSpec - rs: (region: any) => void - rj: (reject: IRejectUserInput) => void -} - -@Injectable({ - providedIn : 'root' -}) - -export class AtlasViewerAPIServices implements OnDestroy{ - - public loadMesh$ = new Subject<ILoadMesh>() - - private onDestoryCb: (() => void)[] = [] - private loadedTemplates$: Observable<any> - private selectParcellation$: Observable<any> - public interactiveViewer: IInteractiveViewerInterface - - public loadedLibraries: Map<string, {counter: number, src: HTMLElement|null}> = new Map() - - public removeBasedOnPr = (pr: Promise<any>, {userInitiated = false} = {}) => { - - const idx = this.getUserToSelectRegion.findIndex(({ promise }) => promise === pr) - if (idx >=0) { - const { rj } = this.getUserToSelectRegion.splice(idx, 1)[0] - this.getUserToSelectRegionUI$.next([...this.getUserToSelectRegion]) - this.zone.run(() => { }) - rj({ userInitiated }) - } - else throw new Error(`This promise has already been fulfilled.`) - - } - - private dismissDialog: () => void - public getUserToSelectRegion: IGetUserSelectRegionPr[] = [] - public getUserToSelectRegionUI$: Subject<IGetUserSelectRegionPr[]> = new Subject() - - public getNextUserRegionSelectHandler: () => IGetUserSelectRegionPr = () => { - if (this.getUserToSelectRegion.length > 0) { - return this.getUserToSelectRegion[this.getUserToSelectRegion.length - 1] - } - else return null - } - - public popUserRegionSelectHandler = () => { - if (this.getUserToSelectRegion.length > 0) { - this.getUserToSelectRegion.pop() - this.getUserToSelectRegionUI$.next([...this.getUserToSelectRegion]) - } - } - - private s: Subscription[] = [] - - private onMouseClick(ev: any): boolean { - const { rs, spec } = this.getNextUserRegionSelectHandler() || {} - if (!!rs) { - - let moSegments - this.store.pipe( - select(uiStateMouseOverSegmentsSelector), - take(1) - ).subscribe(val => moSegments = val) - - /** - * getROI api - */ - if (spec) { - /** - * if spec of overwrite click is for a point - */ - if (spec.type === EnumCustomRegion.POINT) { - this.popUserRegionSelectHandler() - let mousePositionReal - // rather than commiting mousePositionReal in state via action, do a single subscription instead. - // otherwise, the state gets updated way too often - if (window && (window as any).nehubaViewer) { - (window as any).nehubaViewer.mousePosition.inRealSpace - .take(1) - .subscribe(floatArr => { - mousePositionReal = floatArr && Array.from(floatArr).map((val: number) => val / 1e6) - }) - } - rs({ - type: spec.type, - payload: mousePositionReal - }) - return false - } - - /** - * if spec of overwrite click is for a point - */ - if (spec.type === EnumCustomRegion.PARCELLATION_REGION) { - - if (!!moSegments && Array.isArray(moSegments) && moSegments.length > 0) { - this.popUserRegionSelectHandler() - rs({ - type: spec.type, - payload: moSegments - }) - return false - } - } - } else { - /** - * selectARegion API - * TODO deprecate - */ - if (!!moSegments && Array.isArray(moSegments) && moSegments.length > 0) { - this.popUserRegionSelectHandler() - rs(moSegments[0]) - return false - } - } - } - return true - } - - constructor( - private store: Store<IavRootStoreInterface>, - private dialogService: DialogService, - private snackbar: MatSnackBar, - private zone: NgZone, - private pluginService: PluginServices, - @Optional() @Inject(CANCELLABLE_DIALOG) openCancellableDialog: (message: string, options: any) => () => void, - @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor - ) { - if (clickInterceptor) { - const { register, deregister } = clickInterceptor - const onMouseClick = this.onMouseClick.bind(this) - register(onMouseClick) - this.onDestoryCb.push(() => deregister(onMouseClick)) - } - if (openCancellableDialog) { - this.s.push( - this.getUserToSelectRegionUI$.pipe( - distinctUntilChanged(), - switchMap(arr => { - if (this.dismissDialog) { - this.dismissDialog() - this.dismissDialog = null - } - - if (arr.length === 0) return of(null) - - const last = arr[arr.length - 1] - const { message, promise } = last - return race( - from(new Promise(resolve => { - this.dismissDialog = openCancellableDialog(message, { - userCancelCallback: () => { - resolve(last) - }, - ariaLabel: message - }) - })), - from(promise).pipe( - catchError(() => of(null)), - mapTo(null), - ) - ) - }) - ).subscribe(obj => { - if (obj) { - const { promise, rj } = obj - rj({ userInitiated: true }) - this.removeBasedOnPr(promise, { userInitiated: true }) - } - }) - ) - } - - this.loadedTemplates$ = this.store.pipe( - select(viewerStateFetchedTemplatesSelector) - ) - - this.selectParcellation$ = this.store.pipe( - select('viewerState'), - safeFilter('parcellationSelected'), - map(state => state.parcellationSelected), - ) - - this.interactiveViewer = { - metadata : { - selectedTemplateBSubject : this.store.pipe( - select('viewerState'), - safeFilter('templateSelected'), - map(state => state.templateSelected)), - - selectedParcellationBSubject : this.store.pipe( - select('viewerState'), - safeFilter('parcellationSelected'), - map(state => state.parcellationSelected)), - - selectedRegionsBSubject : this.store.pipe( - select('viewerState'), - safeFilter('regionsSelected'), - map(state => state.regionsSelected), - distinctUntilChanged((arr1, arr2) => - arr1.length === arr2.length && - (arr1 as any[]).every((item, index) => item.name === arr2[index].name)), - ), - - loadedTemplates : [], - - // TODO deprecate - regionsLabelIndexMap : new Map(), - - layersRegionLabelIndexMap: new Map(), - - datasetsBSubject : this.store.pipe( - select('dataStore'), - select('fetchedDataEntries'), - startWith([]) - ), - }, - uiHandle : { - getModalHandler : () => { - throw new Error(`uihandle.getModalHandler has been deprecated`) - }, - - /* to be overwritten by atlasViewer.component.ts */ - getToastHandler : () => { - throw new Error('uiHandle.getToastHandler has been deprecated') - }, - - /** - * to be overwritten by atlas - */ - launchNewWidget: (manifest) => this.pluginService.launchNewWidget(manifest) - .then(() => { - // trigger change detection in Angular - // otherwise, model won't be updated until user input - - /* eslint-disable-next-line @typescript-eslint/no-empty-function */ - this.zone.run(() => { }) - }), - - getUserInput: config => this.dialogService.getUserInput(config) , - getUserConfirmation: config => this.dialogService.getUserConfirm(config), - - getUserToSelectARegion: message => { - console.warn(`interactiveViewer.uiHandle.getUserToSelectARegion is becoming deprecated. Use getUserToSelectRoi instead`) - const obj = { - message, - promise: null, - rs: null, - rj: null - } - const pr = new Promise((rs, rj) => { - obj.rs = rs - obj.rj = rj - }) - - obj.promise = pr - - this.getUserToSelectRegion.push(obj) - this.getUserToSelectRegionUI$.next([...this.getUserToSelectRegion]) - this.zone.run(() => { - - }) - return pr - }, - getUserToSelectRoi: (message: string, spec: ICustomRegionSpec) => { - if (!spec || !spec.type) throw new Error(`spec.type must be defined for getUserToSelectRoi`) - const obj = { - message, - spec, - promise: null, - rs: null, - rj: null - } - const pr = new Promise((rs, rj) => { - obj.rs = rs - obj.rj = rj - }) - - obj.promise = pr - - this.getUserToSelectRegion.push(obj) - this.getUserToSelectRegionUI$.next([...this.getUserToSelectRegion]) - this.zone.run(() => { - - }) - return pr - }, - - cancelPromise: pr => { - this.removeBasedOnPr(pr) - - this.zone.run(() => { }) - } - }, - pluginControl: new Proxy({}, { - get: (_, prop) => { - if (prop === 'loadExternalLibraries') return this.pluginService.loadExternalLibraries - if (prop === 'unloadExternalLibraries') return this.pluginService.unloadExternalLibraries - if (typeof prop === 'string') return this.pluginService.pluginHandlersMap.get(prop) - return undefined - } - }) as any, - } - window.interactiveViewer = this.interactiveViewer - this.init() - } - - private init() { - this.loadedTemplates$.subscribe(templates => this.interactiveViewer.metadata.loadedTemplates = templates) - this.selectParcellation$.pipe( - filter(p => !!p && p.regions), - distinctUntilChanged() - ).subscribe(parcellation => { - this.interactiveViewer.metadata.regionsLabelIndexMap = getLabelIndexMap(parcellation.regions) - this.interactiveViewer.metadata.layersRegionLabelIndexMap = getMultiNgIdsRegionsLabelIndexMap(parcellation) - }) - - this.s.push( - this.loadMesh$.subscribe(({ url, id, type, customFragmentColor = null }) => { - if (!this.interactiveViewer.viewerHandle) { - this.snackbar.open('No atlas loaded! Loading mesh failed!', 'Dismiss') - } - this.interactiveViewer.viewerHandle?.loadLayer({ - [id]: { - type: 'mesh', - source: `vtk://${url}`, - shader: `void main(){${customFragmentColor || FRAGMENT_EMIT_RED};}` - } - }) - }) - ) - } - - ngOnDestroy(){ - while (this.onDestoryCb.length > 0) this.onDestoryCb.pop()() - while(this.s.length > 0){ - this.s.pop().unsubscribe() - } - } -} - -export interface IInteractiveViewerInterface { - - metadata: { - selectedTemplateBSubject: Observable<any|null> - selectedParcellationBSubject: Observable<any|null> - selectedRegionsBSubject: Observable<any[]|null> - loadedTemplates: any[] - regionsLabelIndexMap: Map<number, any> | null - layersRegionLabelIndexMap: Map<string, Map<number, any>> - datasetsBSubject: Observable<any[]> - } - - viewerHandle?: IVIewerHandle - - uiHandle: { - getModalHandler: () => void - getToastHandler: () => void - launchNewWidget: (manifest: IPluginManifest) => Promise<any> - getUserInput: (config: IGetUserInputConfig) => Promise<string> - getUserConfirmation: (config: IGetUserConfirmation) => Promise<any> - getUserToSelectARegion: (selectingMessage: any) => Promise<any> - getUserToSelectRoi: (selectingMessage: string, spec?: ICustomRegionSpec) => Promise<any> - cancelPromise: (pr: Promise<any>) => void - } - - pluginControl: { - loadExternalLibraries: (libraries: string[]) => Promise<void> - unloadExternalLibraries: (libraries: string[]) => void - [key: string]: any - } -} - -interface IGetUserConfirmation { - title?: string - message?: string -} - -interface IGetUserInputConfig extends IGetUserConfirmation { - placeholder?: string - defaultValue?: string -} - -export interface IUserLandmark { - name: string - position: [number, number, number] - id: string /* probably use the it to track and remove user landmarks */ - highlight: boolean - color?: [number, number, number] -} - -export enum EnumCustomRegion{ - POINT = 'POINT', - PARCELLATION_REGION = 'PARCELLATION_REGION', -} - -export interface ICustomRegionSpec{ - type: string // type of EnumCustomRegion -} - -export interface IVIewerHandle { - - setNavigationLoc: (coordinates: [number, number, number], realSpace?: boolean) => void - moveToNavigationLoc: (coordinates: [number, number, number], realSpace?: boolean) => void - setNavigationOri: (quat: [number, number, number, number]) => void - moveToNavigationOri: (quat: [number, number, number, number]) => void - showSegment: (labelIndex: number) => void - hideSegment: (labelIndex: number) => void - showAllSegments: () => void - hideAllSegments: () => void - - getLayersSegmentColourMap: () => Map<string, Map<number, {red: number, green: number, blue: number}>> - - applyLayersColourMap: (newLayerColourMap: Map<string, Map<number, {red: number, green: number, blue: number}>>) => void - - loadLayer: (layerobj: any) => any - removeLayer: (condition: {name: string | RegExp}) => string[] - setLayerVisibility: (condition: {name: string|RegExp}, visible: boolean) => void - - add3DLandmarks: (landmarks: IUserLandmark[]) => void - remove3DLandmarks: (ids: string[]) => void - - mouseEvent: Observable<{eventName: string, event: MouseEvent}> - mouseOverNehuba: Observable<{labelIndex: number, foundRegion: any | null}> - mouseOverNehubaLayers: Observable<Array<{layer: {name: string}, segment: any | number }>> - mouseOverNehubaUI: Observable<{ annotation: any, segments: any, landmark: any, customLandmark: any }> - getNgHash: () => string -} - -export type TSetViewerHandle = (viewerHandle: IVIewerHandle) => void - -export const API_SERVICE_SET_VIEWER_HANDLE_TOKEN = new InjectionToken<TSetViewerHandle>('API_SERVICE_SET_VIEWER_HANDLE_TOKEN') - -export const setViewerHandleFactory = (apiService: AtlasViewerAPIServices) => { - return (viewerHandle: IVIewerHandle) => apiService.interactiveViewer.viewerHandle = viewerHandle -} diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index f407f68944dad36fb049901fcc59e9e91fc77f47..15dbb49421acaec542c842562853d946422e64d6 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -12,18 +12,8 @@ import { } from "@angular/core"; import { Store, select } from "@ngrx/store"; import { Observable, Subscription, merge, timer, fromEvent } from "rxjs"; -import { map, filter, distinctUntilChanged, delay, switchMapTo, take, startWith } from "rxjs/operators"; +import { filter, delay, switchMapTo, take, startWith } from "rxjs/operators"; -import { - IavRootStoreInterface, - isDefined, - safeFilter, -} from "../services/stateStore.service"; -import { WidgetServices } from "src/widget"; - -import { LocalFileService } from "src/services/localFile.service"; -import { AGREE_COOKIE } from "src/services/state/uiState.store"; -import { isSame } from "src/util/fn"; import { colorAnimation } from "./atlasViewer.animation" import { MouseHoverDirective } from "src/mouseoverModule"; import {MatSnackBar, MatSnackBarRef} from "@angular/material/snack-bar"; @@ -31,10 +21,11 @@ import {MatDialog, MatDialogRef} from "@angular/material/dialog"; import { ARIA_LABELS, CONST } from 'common/constants' import { SlServiceService } from "src/spotlight/sl-service.service"; -import { PureContantService } from "src/util"; import { ClickInterceptorService } from "src/glue"; import { environment } from 'src/environments/environment' import { DOCUMENT } from "@angular/common"; +import { userPreference } from "src/state" +import { DARKTHEME } from "src/util/injectionTokens"; /** * TODO @@ -57,7 +48,6 @@ const compareFn = (it, item) => it.name === item.name export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public CONST = CONST - public CONTEXT_MENU_ARIA_LABEL = ARIA_LABELS.CONTEXT_MENU public compareFn = compareFn @ViewChild('cookieAgreementComponent', {read: TemplateRef}) public cookieAgreementComponent: TemplateRef<any> @@ -70,66 +60,28 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public ismobile: boolean = false public meetsRequirement: boolean = true - public sidePanelView$: Observable<string|null> - private newViewer$: Observable<any> - private snackbarRef: MatSnackBarRef<any> - public snackbarMessage$: Observable<symbol> public onhoverLandmark$: Observable<{landmarkName: string, datasets: any} | null> private subscriptions: Subscription[] = [] - private selectedParcellation$: Observable<any> public selectedParcellation: any private cookieDialogRef: MatDialogRef<any> constructor( - private store: Store<IavRootStoreInterface>, - private widgetServices: WidgetServices, - private pureConstantService: PureContantService, + private store: Store<any>, private matDialog: MatDialog, private rd: Renderer2, - public localFileService: LocalFileService, private snackbar: MatSnackBar, private el: ElementRef, private slService: SlServiceService, private clickIntService: ClickInterceptorService, - @Inject(DOCUMENT) private document, + @Inject(DOCUMENT) private document: Document, + @Inject(DARKTHEME) private darktheme$: Observable<boolean> ) { - this.snackbarMessage$ = this.store.pipe( - select('uiState'), - select("snackbarMessage"), - ) - - this.sidePanelView$ = this.store.pipe( - select('uiState'), - filter(state => isDefined(state)), - map(state => state.focusedSidePanel), - ) - - this.newViewer$ = this.store.pipe( - select('viewerState'), - select('templateSelected'), - distinctUntilChanged(isSame), - ) - - this.selectedParcellation$ = this.store.pipe( - select('viewerState'), - safeFilter('parcellationSelected'), - map(state => state.parcellationSelected), - distinctUntilChanged(), - ) - - this.subscriptions.push( - this.selectedParcellation$.subscribe(parcellation => { - this.selectedParcellation = parcellation - }), - - ) - const error = this.el.nativeElement.getAttribute('data-error') if (error) { @@ -165,35 +117,13 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { } this.subscriptions.push( - this.pureConstantService.useTouchUI$.subscribe(bool => this.ismobile = bool), - ) - - this.subscriptions.push( - this.snackbarMessage$.pipe( - // angular material issue - // see https://github.com/angular/angular/issues/15634 - // and https://github.com/angular/components/issues/11357 - delay(0), - ).subscribe(messageSymbol => { - if (this.snackbarRef) { this.snackbarRef.dismiss() } - - if (!messageSymbol) { return } - - const message = messageSymbol.description - this.snackbarRef = this.snackbar.open(message, 'Dismiss', { - duration: 5000, - }) - }), - ) - - this.subscriptions.push( - this.newViewer$.subscribe(() => { - this.widgetServices.clearAllWidgets() - }), + this.store.pipe( + select(userPreference.selectors.useMobileUi), + ).subscribe(bool => this.ismobile = bool), ) this.subscriptions.push( - this.pureConstantService.darktheme$.subscribe(flag => { + this.darktheme$.subscribe(flag => { this.rd.setAttribute(this.document.body, 'darktheme', this.meetsRequirement && flag.toString()) }), ) @@ -220,9 +150,8 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { * TODO avoid creating new views in lifecycle hooks in general */ this.store.pipe( - select('uiState'), - select('agreedCookies'), - filter(agreed => !agreed), + select(userPreference.selectors.agreedToCookie), + filter(val => !val), delay(0), ).subscribe(() => { this.cookieDialogRef = this.matDialog.open(this.cookieAgreementComponent) @@ -264,31 +193,23 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { public cookieClickedOk() { if (this.cookieDialogRef) { this.cookieDialogRef.close() } - this.store.dispatch({ - type: AGREE_COOKIE, - }) + this.store.dispatch( + userPreference.actions.agreeCookie() + ) } + private supportEmailAddress = `support@ebrains.eu` public quickTourFinale = { order: 1e6, descriptionMd: `That's it! We hope you enjoy your stay. --- -If you have any comments or need further support, please contact us at [${this.pureConstantService.supportEmailAddress}](mailto:${this.pureConstantService.supportEmailAddress})`, - description: `That's it! We hope you enjoy your stay. If you have any comments or need further support, please contact us at ${this.pureConstantService.supportEmailAddress}`, +If you have any comments or need further support, please contact us at [${this.supportEmailAddress}](mailto:${this.supportEmailAddress})`, + description: `That's it! We hope you enjoy your stay. If you have any comments or need further support, please contact us at ${this.supportEmailAddress}`, position: 'center' } @HostBinding('attr.version') public _version: string = environment.VERSION } - -export interface INgLayerInterface { - name: string - visible: boolean - source: string - type: string // image | segmentation | etc ... - transform?: [[number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number]] | null - // colormap : string -} diff --git a/src/atlasViewer/atlasViewer.style.css b/src/atlasViewer/atlasViewer.style.css index 0f938ac6632a634a35ee793318a408c0e70248fd..3bbc083c049ac6228a4f187f8e599d399c3a8487 100644 --- a/src/atlasViewer/atlasViewer.style.css +++ b/src/atlasViewer/atlasViewer.style.css @@ -11,66 +11,27 @@ display: block; } -ui-nehuba-container -{ - position:absolute; - top:0; - left:0; - width:100%; - height:100%; -} - -layout-floating-container -{ - width:100%; - height:100%; - overflow:hidden; -} - -layout-floating-container > * -{ - position: absolute; - left: 0; - top: 0; -} - -mat-list[dense].contextual-block -{ - display: inline-block; - background-color:rgba(200,200,200,0.8); -} - -:host-context([darktheme="true"]) mat-list[dense].contextual-block -{ - background-color : rgba(30,30,30,0.8); -} - -[fixedMouseContextualContainerDirective] -{ - max-width: 100%; -} div.displayCard { opacity: 0.8; } -mat-sidenav { - box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); -} - -region-menu +.widget-canvas-container { - display:inline-block; -} - -.floating-container -{ - max-width: 350px; + position: absolute; + top: 0; + left: 0; + z-index: 9; + width: 100%; + height: 100%; + pointer-events: none; } - -logo-container +.widget-canvas { - height: 2rem; - opacity: 0.2; + position: absolute; + width: 0; + height: 0; + pointer-events: none; + overflow: visible; } diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html index 5549a8e38914948e7157fcfc6cde6a4f63bd9153..ed2e2c6da37077aeea63cf59942a0b7dc7386986 100644 --- a/src/atlasViewer/atlasViewer.template.html +++ b/src/atlasViewer/atlasViewer.template.html @@ -1,8 +1,3 @@ - -<!-- required for manufacturing plugin templates --> -<div pluginFactoryDirective> -</div> - <ng-container *ngIf="meetsRequirement; else doesNotMeetReqTemplate"> <ng-container *ngTemplateOutlet="viewerBody"> @@ -29,70 +24,24 @@ <!-- atlas template --> <ng-template #viewerBody> <div class="w-100 h-100" - iav-media-query quick-tour [quick-tour-position]="quickTourFinale.position" [quick-tour-description]="quickTourFinale.description" [quick-tour-description-md]="quickTourFinale.descriptionMd" [quick-tour-order]="quickTourFinale.order" [quick-tour-overwrite-arrow]="emptyArrowTmpl" - quick-tour-severity="low" - #media="iavMediaQuery"> + quick-tour-severity="low"> <!-- prevent default is required so that user do not zoom in on UI or scroll on mobile UI --> <iav-cmp-viewer-container class="w-100 h-100 d-block" - [ismobile]="(media.mediaBreakPoint$ | async) > 3" iav-captureClickListenerDirective [iav-captureClickListenerDirective-captureDocument]="true" (iav-captureClickListenerDirective-onUnmovedClick)="mouseClickDocument($event)"> </iav-cmp-viewer-container> - <layout-floating-container - zIndex="13" - #floatingOverlayContainer> - <div floatingContainerDirective> - </div> - - <div *ngIf="(media.mediaBreakPoint$ | async) < 3" - class="fixed-bottom pe-none mb-2 d-flex justify-content-center"> - <ng-container *ngTemplateOutlet="logoTmpl"> - </ng-container> - </div> - - <div *ngIf="!ismobile" floatingMouseContextualContainerDirective> - - <div class="h-0" - iav-mouse-hover - #iavMouseHoverContextualBlock="iavMouseHover"> - </div> - <mat-list dense class="contextual-block"> - - <mat-list-item *ngFor="let cvtOutput of iavMouseHoverContextualBlock.currentOnHoverObs$ | async | mouseoverCvt" - class="h-auto"> - - <mat-icon - [fontSet]="cvtOutput.icon.fontSet" - [fontIcon]="cvtOutput.icon.fontIcon" - mat-list-icon> - </mat-icon> - - <div matLine>{{ cvtOutput.text }}</div> - - </mat-list-item> - </mat-list> - <!-- TODO Potentially implementing plugin contextual info --> - </div> - - <div class="floating-container" - [attr.aria-label]="CONTEXT_MENU_ARIA_LABEL" - fixedMouseContextualContainerDirective - #fixedContainer="iavFixedMouseCtxContainer"> - - <!-- mouse on click context menu, currently not used --> - - </div> - - </layout-floating-container> + <div class="widget-canvas-container"> + <div widget-canvas class="widget-canvas"></div> + </div> </div> </ng-template> @@ -101,11 +50,6 @@ <not-supported-component></not-supported-component> </ng-template> -<!-- logo tmpl --> -<ng-template #logoTmpl> - <logo-container></logo-container> -</ng-template> - <ng-template #idleOverlay> <tryme-component></tryme-component> </ng-template> diff --git a/src/atlasViewerExports/export.html b/src/atlasViewerExports/export.html deleted file mode 100644 index b4a41a088e065668815706796260dc8f76e1faf9..0000000000000000000000000000000000000000 --- a/src/atlasViewerExports/export.html +++ /dev/null @@ -1,169 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=720, initial-scale=1.0"> - <title>Interactive Viewer Exported Components</title> - <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <script src = "https://unpkg.com/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script> - <script src = "export.js"></script> - - <script> - document.addEventListener('DOMContentLoaded',()=>{ - const script = ` -const inputItem = { - "name" : "drinks", - "children" : [{ - "name":"coffee", - "children":[{ - "name":"flatwhite", - "children":[] - },{ - "name":"espresso", - "children":[] - }] - },{ - "name" : "tea", - "children" : [{ - "name" : "green tea", - "children" : [], - },{ - "name" : "black tea", - "children" : [] - }] - }] -} -const tree = document.getElementById('tree-element') -tree.setAttribute('input-item',JSON.stringify(inputItem)) -` - const treeSampleBox = document.getElementById('tree-element-sample-box') - treeSampleBox.setAttribute('script-input',script) - eval(script) - - const tree2 = document.getElementById('tree-element') - tree2.addEventListener('mouseclicktree',(ev)=>console.warn(ev)) - - const markdownIncludeString = ` -You will also need \`bootstrap 3.3.7\` for formatting and \`custom-elements-es5-adapter\` for es5 shim: - -\`\`\` -<${''}script src = "https://unpkg.com/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"><${''}/script> -<${''}link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> -\`\`\` - -To use any of the components, include \`export.js\` in the header: - -\`\`\` -<${''}script src = "third_party/atlasViewer/dist/export/export.js"></${''}script> -\`\`\` - -` - const markdownInclude = document.getElementById('markdown-include') - markdownInclude.setAttribute('markdown',markdownIncludeString) - - const correctMarkDown = ` -\`\`\` -const correctMarkDown = \` -<\$\{''\}script> - console.log('NOT GOTCHA') -<\$\{''\}/script> -\` - -const markdownCorrect = document.getElementById('markdown-id') -markdownCorrect.setAttribute('markdown',correctmarkDown) -\`\`\` -` - const markdownCorrect = document.getElementById('markdown-correct') - markdownCorrect.setAttribute('markdown',correctMarkDown) - }) - </script> -</head> -<body> - - <div class = "jumbotron"> - <div class = "container"> - <div class = "row"> - <h1>interactive viewer components</h1> - </div> - <div class = "row"> -<markdown-element id = "markdown-include"> -</markdown-element> - </div> - </div> - </div> - <div id = "container" class = "container-fluid"> - <div class = "row"> - <div class = "col-md-3"> - -<sample-box sample-box-title = "readmore component"> -<readmore-element> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -</readmore-element> -</sample-box> - </div> - <div class = "col-md-3"> -<sample-box sample-box-title = "markdown component"> -<markdown-element> -# Heading1 -## Heading2 -### Heading3 - -this is a paragraph - -- a dot point -- a second dot point -</markdown-element> -</sample-box> -<h3> - n.b. -</h3> -<p> -<markdown-element> -- first attempts to parse `element.getAttribute('markdown')` then `element.innerHTML`, if non-empty. -- major gotcha: if markdown is rendered as a part of innerHTML, the dom will be rendered briefly. As a result, triple ticked script tags **will** will be evaluated. For example, the following script, if enclosed between `markdown-element` tags, will still be evaluated: - -``` -<script> -console.log('GOTCHA') -</script> -``` - -- To include script tags, pass it as an attribute instead: - -</markdown-element> -<markdown-element id = "markdown-correct"> -</markdown-element> -<markdown-element> -- The `${''}` is required to breakup the `</script>` tag, or the script tag will be terminated prematurely. -</markdown-element> -</p> - </div> - <div class = "col-md-3"> -<sample-box sample-box-title = "panel component"> -<panel-element collapse-body = "false" body-collapsable = "true" show-body = "true" show-footer = "true" iv-parse-attribute> - <div style = "padding: 0.5em;" heading> - panel heading - </div> - <div style = "padding: 0.5em;" body> - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - </div> - <div style = "opacity:0.7;padding: 0.5em;" footer> - panel footer - </div> -</panel-element> -</sample-box> - </div> - <div class = "col-md-3"> -<sample-box sample-box-title = "tree component" id = "tree-element-sample-box"> -<tree-element children-expanded = "true" id = 'tree-element' treebase> - -</tree-element> -</sample-box> - </div> - </div> - </div> -</body> -<footer> -</footer> -</html> \ No newline at end of file diff --git a/src/atlasViewerExports/export.module.ts b/src/atlasViewerExports/export.module.ts deleted file mode 100644 index 1ebe2eabff0294e2b77e9aa6b5adfb9ca45122cc..0000000000000000000000000000000000000000 --- a/src/atlasViewerExports/export.module.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Injector, NgModule } from "@angular/core"; -import { createCustomElement } from '@angular/elements' -import { FormsModule } from "@angular/forms"; -import { BrowserModule } from "@angular/platform-browser"; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations' -import { ComponentsModule } from "../components/components.module"; -import { MarkdownDom } from '../components/markdown/markdown.component' -import { ParseAttributeDirective } from "../components/parseAttribute.directive"; -import { TreeComponent } from "../components/tree/tree.component"; -import { SampleBoxUnit } from "./sampleBox/sampleBox.component"; - -@NgModule({ - imports : [ - BrowserModule, - BrowserAnimationsModule, - FormsModule, - ComponentsModule, - ], - declarations : [ - SampleBoxUnit, - - /* parse element attributes from string to respective datatypes */ - ParseAttributeDirective, - ], - entryComponents : [ - SampleBoxUnit, - - MarkdownDom, - TreeComponent, - ], -}) - -export class ExportModule { - constructor(public injector: Injector) { - const sampleBox = createCustomElement(SampleBoxUnit, {injector: this.injector}) - customElements.define('sample-box', sampleBox) - - const markDown = createCustomElement(MarkdownDom, {injector : this.injector }) - customElements.define('markdown-element', markDown) - - const tree = createCustomElement(TreeComponent, {injector : this.injector }) - customElements.define('tree-element', tree) - - } - - /* eslint-disable-next-line @typescript-eslint/no-empty-function */ - public ngDoBootstrap() {} -} diff --git a/src/atlasViewerExports/main.export.aot.ts b/src/atlasViewerExports/main.export.aot.ts deleted file mode 100644 index c27b91b4ea8d2d86ff7deb6c192f58d7c235ee02..0000000000000000000000000000000000000000 --- a/src/atlasViewerExports/main.export.aot.ts +++ /dev/null @@ -1,6 +0,0 @@ -import 'zone.js' - -import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; -import { ExportModule } from "./export.module"; - -platformBrowserDynamic().bootstrapModule(ExportModule) diff --git a/src/atlasViewerExports/main.export.ts b/src/atlasViewerExports/main.export.ts deleted file mode 100644 index 2bdb1c047520c3ffca666cab10a4f5fba952c510..0000000000000000000000000000000000000000 --- a/src/atlasViewerExports/main.export.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; -import 'reflect-metadata' -import 'zone.js' -import { ExportModule } from "./export.module"; - -platformBrowserDynamic().bootstrapModule(ExportModule) diff --git a/src/atlasViewerExports/sampleBox/sampleBox.component.ts b/src/atlasViewerExports/sampleBox/sampleBox.component.ts deleted file mode 100644 index 3a819430405bf38c6e1951d2910103dec3b5fb4b..0000000000000000000000000000000000000000 --- a/src/atlasViewerExports/sampleBox/sampleBox.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - Component, - ElementRef, - Input, - OnChanges, - OnInit, - Renderer2, - ViewChild, -} from '@angular/core' - -@Component({ - selector : 'sample-box', - templateUrl : './sampleBox.template.html', - styleUrls : [ - './sampleBox.style.css', - ], -}) - -export class SampleBoxUnit implements OnInit, OnChanges { - @Input() public sampleBoxTitle = `` - @Input() public scriptInput - - @ViewChild('ngContent', {read: ElementRef}) public ngContent: ElementRef - - public escapedHtml: string = `` - public escapedScript: string = `` - - private scriptEl: HTMLScriptElement - - constructor(private rd2: Renderer2) { - this.scriptEl = this.rd2.createElement('script') - } - - public ngOnInit() { - this.escapedHtml = this.ngContent.nativeElement.innerHTML - } - - public ngOnChanges() { - this.escapedScript = this.scriptInput - if ( this.scriptInput ) { - this.scriptEl.innerText = this.scriptInput - } - } -} diff --git a/src/atlasViewerExports/sampleBox/sampleBox.template.html b/src/atlasViewerExports/sampleBox/sampleBox.template.html deleted file mode 100644 index b94af69fe74182a7fc5abbbdf9670fd2d3b08237..0000000000000000000000000000000000000000 --- a/src/atlasViewerExports/sampleBox/sampleBox.template.html +++ /dev/null @@ -1,19 +0,0 @@ -<h2> - {{ sampleBoxTitle }} -</h2> -<hr /> -<h3> - Result: -</h3> -<div class = "well" #ngContent> - <ng-content> - </ng-content> -</div> -<h3> - HTML: -</h3> -<pre>{{ escapedHtml }}</pre> -<h3 *ngIf = "scriptInput"> - Script: -</h3> -<pre *ngIf = "scriptInput">{{ escapedScript }}</pre> diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts index 504e7299ca852190d15e4218da246f132bbc6315..b23dc32cea6ee06d5b247c1893873443faf04ff6 100644 --- a/src/auth/auth.service.spec.ts +++ b/src/auth/auth.service.spec.ts @@ -1,7 +1,7 @@ import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing" +import { APP_INITIALIZER } from "@angular/core" import { TestBed } from "@angular/core/testing" import { hot } from "jasmine-marbles" -import { PureContantService } from "src/util" import { AuthService } from "./auth.service" describe('>auth.service.ts', () => { @@ -14,10 +14,13 @@ describe('>auth.service.ts', () => { providers: [ AuthService, { - provide: PureContantService, - useValue: { - backendUrl: `http://localhost:3000/` - } + provide: APP_INITIALIZER, + useFactory: (authSvc: AuthService) => { + authSvc.authReloadState() + return () => Promise.resolve() + }, + multi: true, + deps: [ AuthService ] } ] }) @@ -38,7 +41,7 @@ describe('>auth.service.ts', () => { it('> if http response errors, user$ should be stream of null', () => { - const resp = ctrl.expectOne('http://localhost:3000/user') + const resp = ctrl.expectOne('user') resp.error(null, { status: 404, }) @@ -53,7 +56,7 @@ describe('>auth.service.ts', () => { it('> if http response contains truthy error key, user should return stream of null', () => { - const resp = ctrl.expectOne('http://localhost:3000/user') + const resp = ctrl.expectOne('user') resp.flush({ error: true }) @@ -72,7 +75,7 @@ describe('>auth.service.ts', () => { name: 'foobar', id: 'baz' } - const resp = ctrl.expectOne('http://localhost:3000/user') + const resp = ctrl.expectOne('user') resp.flush(mockUser) expect( diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index bb610daceac50e7a2ae11ef8fe0919f025354fa3..e2067301c80904efaafbc25527a5e58773ce9558 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -2,8 +2,7 @@ import { HttpClient } from "@angular/common/http"; import { Injectable, OnDestroy } from "@angular/core"; import { Observable, of, Subscription } from "rxjs"; import { catchError, map, shareReplay } from "rxjs/operators"; -import { PureContantService } from "src/util"; -import { BACKENDURL } from "src/util/constants"; +import { environment } from "src/environments/environment" const IV_REDIRECT_TOKEN = `IV_REDIRECT_TOKEN` @@ -38,9 +37,8 @@ export class AuthService implements OnDestroy { constructor( private httpClient: HttpClient, - private constantSvc: PureContantService, ) { - this.user$ = this.httpClient.get<TUserRouteResp>(`${this.constantSvc.backendUrl}user`).pipe( + this.user$ = this.httpClient.get<TUserRouteResp>(`${environment.BACKEND_URL || ''}user`).pipe( map(json => { if (json.error) { throw new Error(json.message || 'User not loggedin.') diff --git a/src/components/components.module.ts b/src/components/components.module.ts index c8d9ff210b5530a6e001dcd983a3fb8487c560cf..1a0150f4b785f88253f307a123e60cfe5f1cb428 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -3,27 +3,17 @@ import { NgModule } from '@angular/core' import { FormsModule } from '@angular/forms' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' -import { MarkdownDom } from './markdown/markdown.component'; +import { MarkdownModule } from './markdown'; import { CommonModule } from '@angular/common'; import { AngularMaterialModule } from 'src/sharedModules'; import { UtilModule } from 'src/util'; import { SafeHtmlPipe } from './safeHtml.pipe' -import { TreeSearchPipe } from './treeSearch.pipe'; import { ConfirmDialogComponent } from './confirmDialog/confirmDialog.component'; import { DialogComponent } from './dialog/dialog.component'; -import { AppendSiblingFlagPipe } from './flatTree/appendSiblingFlag.pipe'; -import { ClusteringPipe } from './flatTree/clustering.pipe'; -import { FilterCollapsePipe } from './flatTree/filterCollapse.pipe'; -import { FlattenTreePipe } from './flatTree/flattener.pipe'; -import { FlatTreeComponent } from './flatTree/flatTree.component'; -import { HighlightPipe } from './flatTree/highlight.pipe'; -import { RenderPipe } from './flatTree/render.pipe'; -import { TreeComponent } from './tree/tree.component'; -import { TreeBaseDirective } from './tree/treeBase.directive'; import { IAVVerticalButton } from './vButton/vButton.component'; import { DynamicMaterialBtn } from './dynamicMaterialBtn/dynamicMaterialBtn.component'; -import { SpinnerCmp } from './spinner/spinner.component'; +import { SpinnerModule } from "./spinner" import { ReadmoreModule } from './readmore'; import { TileCmp } from './tile/tile.component'; @@ -36,48 +26,32 @@ import { TileCmp } from './tile/tile.component'; AngularMaterialModule, UtilModule, ReadmoreModule, + SpinnerModule, + MarkdownModule, ], declarations : [ /* components */ - MarkdownDom, - TreeComponent, - FlatTreeComponent, DialogComponent, ConfirmDialogComponent, IAVVerticalButton, DynamicMaterialBtn, - SpinnerCmp, TileCmp, - /* directive */ - TreeBaseDirective, - /* pipes */ SafeHtmlPipe, - TreeSearchPipe, - FlattenTreePipe, - RenderPipe, - HighlightPipe, - AppendSiblingFlagPipe, - ClusteringPipe, - FilterCollapsePipe, ], exports : [ BrowserAnimationsModule, ReadmoreModule, - - MarkdownDom, - TreeComponent, - FlatTreeComponent, + SpinnerModule, + MarkdownModule, + DialogComponent, ConfirmDialogComponent, IAVVerticalButton, DynamicMaterialBtn, - SpinnerCmp, TileCmp, - TreeSearchPipe, - TreeBaseDirective, ], }) diff --git a/src/components/dynamicMaterialBtn/dynamicMaterialBtn.stories.ts b/src/components/dynamicMaterialBtn/dynamicMaterialBtn.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..3c44ad2d5ebf4dfc28212b83c650a4b3283acaba --- /dev/null +++ b/src/components/dynamicMaterialBtn/dynamicMaterialBtn.stories.ts @@ -0,0 +1,50 @@ +import { CommonModule } from "@angular/common" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { AngularMaterialModule } from "src/sharedModules" +import { + DynamicMaterialBtn +} from "./dynamicMaterialBtn.component" + +export default { + component: DynamicMaterialBtn, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + AngularMaterialModule, + ], + }) + ], +} as Meta + +const Template: Story<DynamicMaterialBtn> = (args: DynamicMaterialBtn) => ({ + template: `<iav-dynamic-mat-button>Dynamic Button</iav-dynamic-mat-button>`, + props: args +}) + +export const Default = Template.bind({}) +Default.args = { + matBtnDisabled: false +} + +export const RaiseButton = Template.bind({}) +RaiseButton.args = { + ...Default.args, + matBtnStyle: "mat-raised-button" +} + +export const RaisePrimaryButton = Template.bind({}) +RaisePrimaryButton.args = { + ...Default.args, + matBtnStyle: "mat-raised-button", + matBtnColor: "primary", +} + + +export const RaisePrimaryDisabledButton = Template.bind({}) +RaisePrimaryDisabledButton.args = { + ...Default.args, + matBtnStyle: "mat-raised-button", + matBtnColor: "primary", + matBtnDisabled: true, +} diff --git a/src/components/flatHierarchy/const.ts b/src/components/flatHierarchy/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..b823fab8b090db0122332b2e120b53ab48e18b77 --- /dev/null +++ b/src/components/flatHierarchy/const.ts @@ -0,0 +1,5 @@ +export type TreeNode<T> = { + node: T + level: number + expandable: boolean +} \ No newline at end of file diff --git a/src/components/flatHierarchy/index.ts b/src/components/flatHierarchy/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b276f3038f3f8c46e23bcb6eb59bc12798c1ecc4 --- /dev/null +++ b/src/components/flatHierarchy/index.ts @@ -0,0 +1 @@ +export { SxplrFlatHierarchyModule } from "./module" \ No newline at end of file diff --git a/src/components/flatHierarchy/module.ts b/src/components/flatHierarchy/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..298b0f6675aca984f44cac8f1470d3a63e282d21 --- /dev/null +++ b/src/components/flatHierarchy/module.ts @@ -0,0 +1,25 @@ +import { ScrollingModule } from "@angular/cdk/scrolling"; +import { CdkTreeModule } from "@angular/cdk/tree"; +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { AngularMaterialModule } from "src/sharedModules"; +import { FlatHierarchySpacer } from "./spacer.pipe"; +import { SxplrFlatHierarchyTreeView } from "./treeView/treeView.component"; + +@NgModule({ + imports: [ + CommonModule, + CdkTreeModule, + ScrollingModule, + AngularMaterialModule, + ], + declarations: [ + SxplrFlatHierarchyTreeView, + FlatHierarchySpacer, + ], + exports: [ + SxplrFlatHierarchyTreeView + ] +}) + +export class SxplrFlatHierarchyModule{} \ No newline at end of file diff --git a/src/components/flatHierarchy/spacer.pipe.ts b/src/components/flatHierarchy/spacer.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..a7533b68c759ed72c1a56b87a762409a4d8a5f2b --- /dev/null +++ b/src/components/flatHierarchy/spacer.pipe.ts @@ -0,0 +1,13 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { TreeNode } from "./const" + +@Pipe({ + name: 'flatHierarchySpacer', + pure: true +}) + +export class FlatHierarchySpacer implements PipeTransform{ + public transform<T>(inputNode: TreeNode<T>, ...args: any[]) { + return Array(inputNode.level).fill(null) + } +} diff --git a/src/components/flatHierarchy/treeView.stories.ts b/src/components/flatHierarchy/treeView.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..68eb30565c76f7989c37e3774c649e0e4608a071 --- /dev/null +++ b/src/components/flatHierarchy/treeView.stories.ts @@ -0,0 +1,116 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Component, EventEmitter } from "@angular/core" +import { action } from "@storybook/addon-actions" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SapiRegionModel } from "src/atlasComponents/sapi" +import { atlasId, provideDarkTheme, getParcRegions, parcId, spaceId } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { SxplrFlatHierarchyModule } from "./module" + + +@Component({ + selector: `hierarchy-flatview-wrapper`, + template: ` + <ng-template #tmplRef let-region> + <div class="mat-body sxplr-d-flex sxplr-align-items-center sxplr-h-100 region-tmpl"> + {{ region.name }} + </div> + </ng-template> + <sxplr-flat-hierarchy-tree-view + [sxplr-flat-hierarchy-tree-view-show-control]="showControl" + [sxplr-flat-hierarchy-nodes]="regions" + [sxplr-flat-hierarchy-is-parent]="isParent" + [sxplr-flat-hierarchy-tree-view-node-label-toggles]="nodeLabelToggles" + [sxplr-flat-hierarchy-tree-view-lineheight]="lineHeight" + [sxplr-flat-hierarchy-render-node-tmpl]="tmplRef" + (sxplr-flat-hierarchy-tree-view-node-clicked)="nodeClicked.emit($event)"> + </sxplr-flat-hierarchy-tree-view> + + `, + styles: [ + ` + .region-tmpl + { + text-overflow: clip; + white-space: nowrap; + } + ` + ] +}) + +class HierarchyFlatViewWrapper{ + regions: SapiRegionModel[] = [] + showControl = false + nodeLabelToggles = true + lineHeight: number = 35 + nodeClicked = new EventEmitter<SapiRegionModel>() + isParent(region: SapiRegionModel, parentRegion: SapiRegionModel) { + return region.hasParent?.some(parent => parent['@id'] === parentRegion["@id"]) + } +} + +export default { + component: HierarchyFlatViewWrapper, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SxplrFlatHierarchyModule, + AngularMaterialModule, + ], + providers: [ + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<HierarchyFlatViewWrapper> = (args: HierarchyFlatViewWrapper, { loaded, parameters }) => { + const { + regions + } = loaded + + const { + + } = parameters + + const { + showControl, + nodeLabelToggles, + lineHeight, + } = args + return ({ + props: { + regions, + showControl: !!showControl, + nodeLabelToggles: !!nodeLabelToggles, + lineHeight: lineHeight || 35, + nodeClicked: { + emit: action("nodeClicked") + } + }, + }) +} +Template.loaders = [] + +const asyncLoader = async (atlasId: string, parcId: string, spaceId: string) => { + const regions = await getParcRegions(atlasId, parcId, spaceId) + return { + regions + } +} + +export const Default = Template.bind({}) +Default.loaders = [ + async () => { + const { + regions + } = await asyncLoader(atlasId.human, parcId.human.jba29, spaceId.human.mni152) + return { + regions + } + } +] diff --git a/src/components/flatHierarchy/treeView/treeControl.ts b/src/components/flatHierarchy/treeView/treeControl.ts new file mode 100644 index 0000000000000000000000000000000000000000..7471dfbb839cb36c23413d026c2f9c8f607af6dd --- /dev/null +++ b/src/components/flatHierarchy/treeView/treeControl.ts @@ -0,0 +1,100 @@ + +type IsParent<T> = (child: T, parent: T) => boolean + +export class Tree<T extends Record<string, unknown>>{ + + private _nodes: T[] = [] + protected set nodes(nodes: T[]) { + if (nodes === this._nodes) return + this._nodes = nodes + this.resetWeakMaps() + } + protected get nodes() { + return this._nodes + } + + private _isParent: IsParent<T> = (c, p) => false + protected set isParent(fn: IsParent<T>){ + if (fn === this._isParent) return + this._isParent = fn + this.resetWeakMaps() + } + protected get isParent(){ + return this._isParent + } + public someAncestor(node: T, predicate: (anc: T) => boolean) { + const parent = this.getParent(node) + if (!parent) { + return false + } + if (predicate(parent)) { + return true + } + return this.someAncestor(parent, predicate) + } + public getParent(node: T): T { + if (!this.parentWeakMap.has(node)) { + for (const parentNode of this.nodes) { + if (this.isParent(node, parentNode)) { + this.parentWeakMap.set(node, parentNode) + break + } + } + } + return this.parentWeakMap.get(node) + } + public getChildren(node: T): T[] { + if (!this.childrenWeakMap.has(node)) { + const children = this.nodes.filter(childNode => this.isParent(childNode, node)) + this.childrenWeakMap.set(node, children) + for (const c of children) { + this.parentWeakMap.set(c, node) + } + } + return this.childrenWeakMap.get(node) + } + public getLevel(node: T): number { + let level = -1, cursor = node + const maxLevel = 32 + do { + if (level >= maxLevel) { + level = 0 + break + } + level++ + cursor = this.getParent(cursor) + } while (!!cursor) + return level + } + protected childrenWeakMap = new WeakMap<T, T[]>() + protected parentWeakMap = new WeakMap<T, T>() + protected nodeAncestorIsLast = new WeakMap<T, boolean[]>() + + get rootNodes() { + const root = this.nodes.filter(node => !this.getParent(node)) + for (const el of root) { + this.parentWeakMap.set(el, null) + this.nodeAncestorIsLast.set(el, []) + } + return root + } + + constructor( + _nodes: T[] = [], + _isParent: IsParent<T> = (c, p) => false + ){ + this._nodes = _nodes + this._isParent = _isParent + } + + private resetWeakMaps() { + + /** + * on new nodes passed, reset all maps + * otherwise, tree will show stale data and will not update + */ + this.childrenWeakMap = new WeakMap() + this.parentWeakMap = new WeakMap() + this.nodeAncestorIsLast = new WeakMap() + } +} diff --git a/src/components/flatHierarchy/treeView/treeView.component.ts b/src/components/flatHierarchy/treeView/treeView.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..f67f7cdc8a157e42c763d63021cf052f594a3c1e --- /dev/null +++ b/src/components/flatHierarchy/treeView/treeView.component.ts @@ -0,0 +1,131 @@ +import { FlatTreeControl } from "@angular/cdk/tree"; +import { MatTreeFlatDataSource, MatTreeFlattener } from "@angular/material/tree" +import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, OnChanges, Output, SimpleChanges, TemplateRef } from "@angular/core"; +import { TreeNode } from "../const" +import { Tree } from "./treeControl" + +@Component({ + selector: `sxplr-flat-hierarchy-tree-view`, + templateUrl: `./treeView.template.html`, + styleUrls: [ + `./treeView.style.css` + ], + changeDetection: ChangeDetectionStrategy.OnPush, + exportAs: 'flatHierarchyTreeView' +}) + +export class SxplrFlatHierarchyTreeView<T extends Record<string, unknown>> extends Tree<T> implements OnChanges{ + @HostBinding('class') + class = 'sxplr-custom-cmp' + + @Input('sxplr-flat-hierarchy-nodes') + sxplrNodes: T[] = [] + + @Input('sxplr-flat-hierarchy-is-parent') + sxplrIsParent: (child: T, parent: T) => boolean + + @Input('sxplr-flat-hierarchy-render-node-tmpl') + renderNodeTmplRef: TemplateRef<T> + + @Input('sxplr-flat-hierarchy-tree-view-lineheight') + lineHeight: number = 35 + + @Input('sxplr-flat-hierarchy-tree-view-show-control') + showControl: boolean = false + + @Input('sxplr-flat-hierarchy-tree-view-node-label-toggles') + nodeLabelToggles: boolean = true + + @Input('sxplr-flat-hierarchy-tree-view-expand-on-init') + expandOnInit: boolean = true + + @Output('sxplr-flat-hierarchy-tree-view-node-clicked') + nodeClicked = new EventEmitter<T>() + + ngOnChanges(changes: SimpleChanges): void { + if (changes.sxplrNodes || changes.sxplrIsParent) { + this.nodes = this.sxplrNodes + this.isParent = this.sxplrIsParent + this.dataSource.data = this.rootNodes + if (this.expandOnInit) { + this.treeControl.expandAll() + } + } + } + + private getNodeAncestorIsLast(node: T, visited = new WeakSet<T>() ): boolean[] { + if (!this.nodeAncestorIsLast.has(node)) { + if (visited.has(node)) { + console.error(`getNodeAncestorIsLast visited node twice:`, node) + throw new Error(`getNodeAncestorIsLast visited node twice. Check console for more details.`) + } + visited.add(node) + + const parent = this.getParent(node) + const children = this.getChildren(parent) + if (!children.includes(node)) { + console.error(`getNodeAncestorIsLast node is not included in the parent's children!`, node, parent, children) + throw new Error(`getNodeAncestorIsLast node is not included in the parent's children! Check console for more details.`) + } + const isLast = children.indexOf(node) === (children.length - 1) + + const ancestors = this.getNodeAncestorIsLast(parent, visited) + this.nodeAncestorIsLast.set(node, [ ...ancestors, isLast ]) + } + return this.nodeAncestorIsLast.get(node) + } + + public treeControl = new FlatTreeControl<TreeNode<T>>( + ({ level }) => level, + ({ expandable }) => expandable, + ) + + private treeFlattener = new MatTreeFlattener<T, TreeNode<T>>( + (node: T, level: number) => { + return { + node, + level, + expandable: this.getChildren(node).length > 0, + } + }, + ({ level }) => level, + ({ expandable }) => expandable, + node => this.getChildren(node) + ) + + public dataSource = new MatTreeFlatDataSource<T, TreeNode<T>>( + this.treeControl, + this.treeFlattener + ) + + hasChild(_: number, node: TreeNode<T>) { + return node.expandable + } + + getSpacerClass(node: TreeNode<T>, spacerIdx: number){ + const arrBool = this.getNodeAncestorIsLast(node.node).slice(0, -1) + if (arrBool[spacerIdx]) { + return `lvl-hide-left-border` + } + return `` + } + + handleClickNode(node: TreeNode<T>){ + if (this.nodeLabelToggles) { + this.treeControl.toggle(node) + } + this.nodeClicked.emit(node.node) + } + + expandAll(){ + this.treeControl.expandAll() + } + + collaseAll(){ + this.treeControl.collapseAll() + } + + constructor(){ + super() + } +} diff --git a/src/components/flatHierarchy/treeView/treeView.style.css b/src/components/flatHierarchy/treeView/treeView.style.css new file mode 100644 index 0000000000000000000000000000000000000000..3ab293efba0d79a7b5bbe555693ed97b7a94a009 --- /dev/null +++ b/src/components/flatHierarchy/treeView/treeView.style.css @@ -0,0 +1,48 @@ +cdk-virtual-scroll-viewport +{ + height: 100%; + min-height: 24rem; + overflow-x: hidden; +} + +.virtual-scroll-container +{ + display: flex; + align-items: center; +} + +.ph-container +{ + transform: translate(20px, -50%); + flex: auto 0 0; + height: 100%; +} + +.ph-container .ph +{ + height: 100%; + width: 2rem; + display: inline-block; +} + +.ph-container .lvl:not(.lvl-hide-left-border) +{ + border-left: 1px dashed currentColor; +} + +.ph-container .lvl:last-child +{ + border-bottom: 1px dashed currentColor; +} + + +cdk-tree-node .node-render-tmpl +{ + flex: 0 1 1; + display: inline-block; +} + +.label-toggles:hover +{ + cursor: default; +} diff --git a/src/components/flatHierarchy/treeView/treeView.template.html b/src/components/flatHierarchy/treeView/treeView.template.html new file mode 100644 index 0000000000000000000000000000000000000000..c35e36cf8cd85e639d99b8ca6bbfd8dd5d466b65 --- /dev/null +++ b/src/components/flatHierarchy/treeView/treeView.template.html @@ -0,0 +1,70 @@ +<ng-template [ngIf]="showControl"> + <button + mat-button + (click)="treeControl.collapseAll()"> + Collapse All + </button> + + <button + mat-button + (click)="treeControl.expandAll()"> + Expand All + </button> +</ng-template> + + +<cdk-virtual-scroll-viewport [itemSize]="lineHeight"> + + <div class="virtual-scroll-container" + *cdkVirtualFor="let node of dataSource" + [style.height.px]="lineHeight"> + + <!-- directory ruling template --> + <ng-template + [ngTemplateOutlet]="phTmpl" + [ngTemplateOutletContext]="{ + $implicit: node + }"> + </ng-template> + + <!-- expandable button --> + <ng-template [ngIf]="node.expandable" [ngIfElse]="btnFallBackTmpl"> + <button mat-icon-button + [ngClass]="{ + 'r-270': !treeControl.isExpanded(node) + }" + (click)="treeControl.toggle(node)"> + <i class="fas fa-chevron-down"></i> + </button> + </ng-template> + + <!-- non expandable button --> + <ng-template #btnFallBackTmpl> + <button mat-icon-button disabled></button> + </ng-template> + + + <!-- template to render the node --> + <div class="node-render-tmpl" + [ngClass]="nodeLabelToggles ? 'label-toggles' : ''" + (click)="handleClickNode(node)"> + <ng-template + [ngTemplateOutlet]="renderNodeTmplRef" + [ngTemplateOutletContext]="{ + $implicit: node.node + }"> + </ng-template> + </div> + + </div> + +</cdk-virtual-scroll-viewport> + +<ng-template #phTmpl let-node> + <div class="ph-container sxplr-pe-none"> + <div class="ph lvl" + [ngClass]="getSpacerClass(node, index)" + *ngFor="let spacer of (node | flatHierarchySpacer); index as index"> + </div> + </div> +</ng-template> diff --git a/src/components/flatTree/appendSiblingFlag.pipe.ts b/src/components/flatTree/appendSiblingFlag.pipe.ts deleted file mode 100644 index c1a43fbd0411d714f6a1109f2d7379402d439879..0000000000000000000000000000000000000000 --- a/src/components/flatTree/appendSiblingFlag.pipe.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name : 'appendSiblingFlagPipe', -}) - -export class AppendSiblingFlagPipe implements PipeTransform { - public transform(objs: any[]): any[] { - return objs - .reduceRight((acc, curr) => ({ - acc : acc.acc.concat(Object.assign({}, curr, { - siblingFlags : curr.lvlId.split('_').map((v, idx) => typeof acc.flags[idx] !== 'undefined' - ? acc.flags[idx] - : false) - .slice(1) - .map(v => !v), - })), - flags: curr.lvlId.split('_').map((_, idx) => acc.flags[idx] ).slice(0, -1).concat(true), - }), { acc: [], flags : Array(256).fill(false) }) - .acc.reverse() - } -} diff --git a/src/components/flatTree/clustering.pipe.ts b/src/components/flatTree/clustering.pipe.ts deleted file mode 100644 index 2d0d11294b46564d4737087abf5f620b20b56b58..0000000000000000000000000000000000000000 --- a/src/components/flatTree/clustering.pipe.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -// TODO deprecate? - -@Pipe({ - name : 'clusteringPipe', -}) - -export class ClusteringPipe implements PipeTransform { - public transform(array: any[], num: number = 100): any[][] { - return array.reduce((acc, curr, idx, arr) => idx % num === 0 - ? acc.concat([arr.slice(idx, idx + num)]) - : acc , []) - } -} diff --git a/src/components/flatTree/filterCollapse.pipe.ts b/src/components/flatTree/filterCollapse.pipe.ts deleted file mode 100644 index e936d7c7b486a70d1db83c9fbb4fa9b5125c7f98..0000000000000000000000000000000000000000 --- a/src/components/flatTree/filterCollapse.pipe.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name: 'filterCollapsePipe', -}) - -export class FilterCollapsePipe implements PipeTransform { - public transform(array: any[], collapsedLevels: Set<string>, uncollapsedLevels: Set<string>, defaultCollapse: boolean ) { - const isCollapsedById = (id) => { - - return collapsedLevels.has(id) - ? true - : uncollapsedLevels.has(id) - ? false - : !defaultCollapse - } - const returnArray = array.filter(item => { - return !item.lvlId.split('_') - .filter((v, idx, arr) => idx < arr.length - 1 ) - .reduce((acc, curr) => acc - .concat(acc.length === 0 - ? curr - : acc[acc.length - 1].concat(`_${curr}`)), []) - .some(id => isCollapsedById(id)) - }) - return returnArray - } -} diff --git a/src/components/flatTree/filterRowsByVisibility.pipe.ts b/src/components/flatTree/filterRowsByVisibility.pipe.ts deleted file mode 100644 index f589fbf8b9a9e05bcfc0a1fce15fee53f2af32d2..0000000000000000000000000000000000000000 --- a/src/components/flatTree/filterRowsByVisibility.pipe.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -// TODO fix typo - -@Pipe({ - name : 'filterRowsByVisbilityPipe', -}) - -export class FilterRowsByVisbilityPipe implements PipeTransform { - public transform(rows: any[], getChildren: (item: any) => any[], filterFn: (item: any) => boolean) { - - return rows.filter(row => this.recursive(row, getChildren, filterFn) ) - } - - private recursive(single: any, getChildren: (item: any) => any[], filterFn: (item: any) => boolean): boolean { - return filterFn(single) || (getChildren && getChildren(single).some(c => this.recursive(c, getChildren, filterFn))) - } -} diff --git a/src/components/flatTree/flatTree.component.ts b/src/components/flatTree/flatTree.component.ts deleted file mode 100644 index d8bd95fb0d5d780595caa03c1695e2763033ee8e..0000000000000000000000000000000000000000 --- a/src/components/flatTree/flatTree.component.ts +++ /dev/null @@ -1,107 +0,0 @@ -import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling"; -import {AfterViewChecked, ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild} from "@angular/core"; -import { IFlattenedTreeInterface } from "./flattener.pipe"; - -/** - * TODO to be replaced by virtual scrolling when ivy is in stable - */ - -@Component({ - selector : 'flat-tree-component', - templateUrl : './flatTree.template.html', - styleUrls : [ - './flatTree.style.css', - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) - -export class FlatTreeComponent implements AfterViewChecked { - @Input() public inputItem: any = { - name : 'Untitled', - children : [], - } - @Input() public childrenExpanded: boolean = true - - @Input() public useDefaultList: boolean = false - - @Output() public treeNodeClick: EventEmitter<any> = new EventEmitter() - - /* highly non-performant. rerenders each time on mouseover or mouseout */ - // @Output() treeNodeEnter : EventEmitter<any> = new EventEmitter() - // @Output() treeNodeLeave : EventEmitter<any> = new EventEmitter() - - @Input() public renderNode: (item: any) => string = (item) => item.name - @Input() public findChildren: (item: any) => any[] = (item) => item.children ? item.children : [] - @Input() public searchFilter: (item: any) => boolean | null = () => true - - @ViewChild('flatTreeVirtualScrollViewPort') public virtualScrollViewPort: CdkVirtualScrollViewport - @Output() public totalRenderedListChanged = new EventEmitter<{ previous: number, current: number }>() - private totalDataLength: number = null - - public flattenedItems: any[] = [] - - public getClass(level: number) { - return [...Array(level + 1)].map((v, idx) => `render-node-level-${idx}`).join(' ') - } - - public collapsedLevels: Set<string> = new Set() - public uncollapsedLevels: Set<string> = new Set() - - public ngAfterViewChecked() { - /** - * if useDefaultList is true, virtualscrollViewPort will be undefined - */ - if (!this.virtualScrollViewPort) { - return - } - const currentTotalDataLength = this.virtualScrollViewPort.getDataLength() - const previousDataLength = this.totalDataLength - - this.totalRenderedListChanged.emit({ - current: currentTotalDataLength, - previous: previousDataLength, - }) - this.totalDataLength = currentTotalDataLength - } - - public toggleCollapse(flattenedItem: IFlattenedTreeInterface) { - if (this.isCollapsed(flattenedItem)) { - this.collapsedLevels.delete(flattenedItem.lvlId) - this.uncollapsedLevels.add(flattenedItem.lvlId) - } else { - this.collapsedLevels.add(flattenedItem.lvlId) - this.uncollapsedLevels.delete(flattenedItem.lvlId) - } - this.collapsedLevels = new Set(this.collapsedLevels) - this.uncollapsedLevels = new Set(this.uncollapsedLevels) - } - - public isCollapsed(flattenedItem: IFlattenedTreeInterface): boolean { - return this.isCollapsedById(flattenedItem.lvlId) - } - - public isCollapsedById(id: string): boolean { - return this.collapsedLevels.has(id) - ? true - : this.uncollapsedLevels.has(id) - ? false - : !this.childrenExpanded - } - - public collapseRow(flattenedItem: IFlattenedTreeInterface): boolean { - return flattenedItem.lvlId.split('_') - .filter((v, idx, arr) => idx < arr.length - 1 ) - .reduce((acc, curr) => acc - .concat(acc.length === 0 - ? curr - : acc[acc.length - 1].concat(`_${curr}`)), []) - .some(id => this.isCollapsedById(id)) - } - - public handleTreeNodeClick(event: MouseEvent, inputItem: any) { - this.treeNodeClick.emit({ - event, - inputItem, - }) - } -} diff --git a/src/components/flatTree/flatTree.style.css b/src/components/flatTree/flatTree.style.css deleted file mode 100644 index f501aada1d1758cdc2bcc57c1d9c12889282bb64..0000000000000000000000000000000000000000 --- a/src/components/flatTree/flatTree.style.css +++ /dev/null @@ -1,186 +0,0 @@ -:host -{ - display: flex; - flex-direction: column; - height:100%; -} - -cdk-virtual-scroll-viewport, -.default-container -{ - flex: 1 0 auto; - overflow-x: hidden; -} - -.render-node-text -{ - white-space: nowrap; - cursor:default; -} - -.padding-block-container -{ - position:absolute; - left:0; - top:0; - height:1.5em; - width: 0px; - white-space:nowrap; -} - -span[renderText] -{ - white-space: nowrap; - overflow: hidden; -} - -.padding-block -{ - display: inline-block; - width: 1em; - height:1.5em; - position:relative; -} - -.padding-block[hidemargin="false"]:before -{ - pointer-events: none; - content: ' '; - position:absolute; - width:100%; - height:100%; - left:0.5em; - top:-0.75em; - border-left:rgba(128,128,128,0.6) 1px dashed; -} - -.render-node-text -{ - opacity: 0.8; -} - -.render-node-text:hover -{ - opacity: 1.0; -} - -.render-node-level-1 -{ - position:relative; - white-space: nowrap; -} - -.render-node-level-1:before -{ - pointer-events: none; - content: ' '; - height:1.5em; - width: 1.5em; - bottom: 0.25em; - left: -0.5em; - position: absolute; - border-left: rgba(128,128,128,0.6) 1px dashed; - border-bottom: rgba(128,128,128,0.6) 1px dashed; -} - -.render-node-level-0 -{ - margin-left: 0em; -} -.render-node-level-1 -{ - margin-left: 1em; -} -.render-node-level-2 -{ - margin-left: 2em; -} -.render-node-level-3 -{ - margin-left: 3em; -} -.render-node-level-4 -{ - margin-left: 4em; -} -.render-node-level-5 -{ - margin-left: 5em; -} -.render-node-level-6 -{ - margin-left: 6em; -} -.render-node-level-7 -{ - margin-left: 7em; -} -.render-node-level-8 -{ - margin-left: 8em; -} -.render-node-level-9 -{ - margin-left: 9em; -} -.render-node-level-10 -{ - margin-left: 10em; -} - - - -.render-node-level-0 > .padding-block-container -{ - left: -0em; -} -.render-node-level-1 > .padding-block-container -{ - left: -1em; -} -.render-node-level-2 > .padding-block-container -{ - left: -2em; -} -.render-node-level-3 > .padding-block-container -{ - left: -3em; -} -.render-node-level-4 > .padding-block-container -{ - left: -4em; -} -.render-node-level-5 > .padding-block-container -{ - left: -5em; -} -.render-node-level-6 > .padding-block-container -{ - left: -6em; -} -.render-node-level-7 > .padding-block-container -{ - left: -7em; -} -.render-node-level-8 > .padding-block-container -{ - left: -8em; -} -.render-node-level-9 > .padding-block-container -{ - left: -9em; -} -.render-node-level-10 > .padding-block-container -{ - left: -10em; -} - -.r-270 -{ - transform: rotate(270deg); -} - -[renderNode] -{ - height: 15px; -} diff --git a/src/components/flatTree/flatTree.template.html b/src/components/flatTree/flatTree.template.html deleted file mode 100644 index 893c539a9ff029933606880e3e37776e3d7fa0c1..0000000000000000000000000000000000000000 --- a/src/components/flatTree/flatTree.template.html +++ /dev/null @@ -1,79 +0,0 @@ -<cdk-virtual-scroll-viewport - *ngIf="!useDefaultList" - (wheel)="$event.stopPropagation()" - itemSize="15" - #flatTreeVirtualScrollViewPort> - - <div - *cdkVirtualFor="let flattenedItem of (inputItem | flattenTreePipe : findChildren | filterRowsByVisbilityPipe : findChildren : searchFilter | appendSiblingFlagPipe | filterCollapsePipe : collapsedLevels : uncollapsedLevels : childrenExpanded )" - [ngClass]="getClass(flattenedItem.flattenedTreeLevel)" - class="text-nowrap" - [attr.flattenedtreelevel]="flattenedItem.flattenedTreeLevel" - [attr.collapsed]="flattenedItem.collapsed ? flattenedItem.collapsed : false" - [attr.lvlId]="flattenedItem.lvlId" - renderNode> - - <span class="padding-block-container"> - <span - *ngFor="let block of flattenedItem.siblingFlags" - [attr.hidemargin]="block" - class="padding-block"> - - </span> - </span> - <span - *ngIf="findChildren(flattenedItem).length > 0; else noChildren" - (click)="$event.stopPropagation(); toggleCollapse(flattenedItem)" > - <i [ngClass]="isCollapsed(flattenedItem) ? 'r-270' : ''" class="fas fa-chevron-down"></i> - </span> - <span - (click)="handleTreeNodeClick($event, flattenedItem)" - class="render-node-text" - [innerHtml]="flattenedItem | renderPipe : renderNode "> - </span> - </div> -</cdk-virtual-scroll-viewport> - -<div - *ngIf="useDefaultList" - class="overflow-auto"> - - <div class="overflow-hidden default-container"> - <div - *ngFor="let flattenedItem of (inputItem | flattenTreePipe : findChildren | filterRowsByVisbilityPipe : findChildren : searchFilter | appendSiblingFlagPipe )" - [ngClass]="getClass(flattenedItem.flattenedTreeLevel)" - class="text-nowrap" - [attr.flattenedtreelevel]="flattenedItem.flattenedTreeLevel" - [attr.collapsed]="flattenedItem.collapsed ? flattenedItem.collapsed : false" - [attr.lvlId]="flattenedItem.lvlId" - [hidden]="collapseRow(flattenedItem) " - renderNode> - - <span class="padding-block-container"> - <span - *ngFor="let block of flattenedItem.siblingFlags" - [attr.hidemargin]="block" - class="padding-block"> - - </span> - </span> - <span - *ngIf="findChildren(flattenedItem).length > 0; else noChildren" - (click)="$event.stopPropagation(); toggleCollapse(flattenedItem)" > - <i [ngClass]="isCollapsed(flattenedItem) ? 'r-270' : ''" class="fas fa-chevron-down"></i> - </span> - <span - (click)="handleTreeNodeClick($event, flattenedItem)" - class="render-node-text" - [innerHtml]="flattenedItem | renderPipe : renderNode "> - </span> - </div> - </div> -</div> - - -<ng-template #noChildren> - <i class="fas fa-none"> - - </i> -</ng-template> \ No newline at end of file diff --git a/src/components/flatTree/flattener.pipe.ts b/src/components/flatTree/flattener.pipe.ts deleted file mode 100644 index d8d017a541e89359ba81f61204adf1040a57883e..0000000000000000000000000000000000000000 --- a/src/components/flatTree/flattener.pipe.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name : 'flattenTreePipe', -}) - -export class FlattenTreePipe implements PipeTransform { - public transform(root: any, findChildren: (root: any) => any[]): any&IFlattenedTreeInterface[] { - return this.recursiveFlatten(root, findChildren, 0, '0') - } - - private recursiveFlatten(obj, findChildren, flattenedTreeLevel, lvlId) { - return [ - this.attachLvlAndLvlIdAndSiblingFlag( - obj, - flattenedTreeLevel, - lvlId, - ), - ].concat( - ...findChildren(obj) - .map((c, idx) => this.recursiveFlatten(c, findChildren, flattenedTreeLevel + 1, `${lvlId}_${idx}` )), - ) - } - - private attachLvlAndLvlIdAndSiblingFlag(obj: any, flattenedTreeLevel: number, lvlId: string) { - return Object.assign({}, obj, { - flattenedTreeLevel, - collapsed : typeof obj.collapsed === 'undefined' ? false : true, - lvlId, - }) - } - -} - -export interface IFlattenedTreeInterface { - flattenedTreeLevel: number - lvlId: string -} diff --git a/src/components/flatTree/highlight.pipe.ts b/src/components/flatTree/highlight.pipe.ts deleted file mode 100644 index 5976ba20b0abf0739390e6b2497a620c0f347735..0000000000000000000000000000000000000000 --- a/src/components/flatTree/highlight.pipe.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { DomSanitizer } from "@angular/platform-browser"; - -@Pipe({ - name : 'highlightPipe', -}) - -export class HighlightPipe implements PipeTransform { - constructor(private sanitizer: DomSanitizer) { - - } - public transform(input: string, searchTerm: string) { - return searchTerm && searchTerm !== '' - ? this.sanitizer.bypassSecurityTrustHtml(input.replace(new RegExp( searchTerm, 'gi'), (s) => `<span class = "highlight">${s}</span>`)) - : input - } -} diff --git a/src/components/flatTree/render.pipe.ts b/src/components/flatTree/render.pipe.ts deleted file mode 100644 index 0b2707fcd9c135fad134ed9ed3d094eb43d28582..0000000000000000000000000000000000000000 --- a/src/components/flatTree/render.pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name : 'renderPipe', -}) - -export class RenderPipe implements PipeTransform { - public transform(node: any, renderFunction: (node: any) => string): string { - return renderFunction(node) - } -} diff --git a/src/components/markdown/index.ts b/src/components/markdown/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..8751618833a80782c23e4f6d8f8fda2401e2c455 --- /dev/null +++ b/src/components/markdown/index.ts @@ -0,0 +1,2 @@ +export { MarkdownModule } from "./module" +export { MarkdownDom } from "./markdownCmp/markdown.component" \ No newline at end of file diff --git a/src/components/markdown/markdown.component.ts b/src/components/markdown/markdownCmp/markdown.component.ts similarity index 73% rename from src/components/markdown/markdown.component.ts rename to src/components/markdown/markdownCmp/markdown.component.ts index bd5ea00320923c15e4517232021822b91fd8e630..7084626a5ed37fb4162d610edfe232fcf3852381 100644 --- a/src/components/markdown/markdown.component.ts +++ b/src/components/markdown/markdownCmp/markdown.component.ts @@ -1,4 +1,5 @@ -import { ChangeDetectionStrategy, Component, ElementRef, Input, ViewChild, ChangeDetectorRef, AfterViewChecked, OnChanges } from '@angular/core' +import { ChangeDetectionStrategy, Component, ElementRef, Input, ViewChild, ChangeDetectorRef, AfterViewChecked, OnChanges, SecurityContext } from '@angular/core' +import { DomSanitizer } from '@angular/platform-browser' import * as showdown from 'showdown' @Component({ @@ -19,7 +20,8 @@ export class MarkdownDom implements OnChanges { }) constructor( - private cdr: ChangeDetectorRef + private cdr: ChangeDetectorRef, + private ds: DomSanitizer, ) { this.cdr.detach() this.converter.setFlavor('github') @@ -32,9 +34,13 @@ export class MarkdownDom implements OnChanges { } ngOnChanges(){ - this.innerHtml = this.converter.makeHtml( + const innerHtml = this.converter.makeHtml( this.getMarkdown() ) + this.innerHtml = this.ds.sanitize( + SecurityContext.HTML, + innerHtml + ) this.cdr.detectChanges() } diff --git a/src/components/markdown/markdownCmp/markdown.style.css b/src/components/markdown/markdownCmp/markdown.style.css new file mode 100644 index 0000000000000000000000000000000000000000..0645dc01a568740a2c3d2f51c486adcb7f111a02 --- /dev/null +++ b/src/components/markdown/markdownCmp/markdown.style.css @@ -0,0 +1,4 @@ +:host +{ + display: block; +} \ No newline at end of file diff --git a/src/components/markdown/markdown.template.html b/src/components/markdown/markdownCmp/markdown.template.html similarity index 67% rename from src/components/markdown/markdown.template.html rename to src/components/markdown/markdownCmp/markdown.template.html index f4a643338901034ec807c46393c68e4c7b8b29df..ae217e43cef6c30161ce1d6ff048bd8272ab69b9 100644 --- a/src/components/markdown/markdown.template.html +++ b/src/components/markdown/markdownCmp/markdown.template.html @@ -1,4 +1,4 @@ -<div [innerHTML]="innerHtml | safeHtml"> +<div [innerHTML]="innerHtml"> </div> <div class="d-none" #ngContentWrapper> <ng-content> diff --git a/src/components/markdown/module.ts b/src/components/markdown/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..7dd8368aea81ebc68f7091175d83971796a00ffb --- /dev/null +++ b/src/components/markdown/module.ts @@ -0,0 +1,17 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { MarkdownDom } from "./markdownCmp/markdown.component"; + +@NgModule({ + imports: [ + CommonModule, + ], + declarations: [ + MarkdownDom + ], + exports: [ + MarkdownDom + ] +}) + +export class MarkdownModule{} diff --git a/src/components/spinner/index.ts b/src/components/spinner/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b6eb0631154fb743ba8e6f4e4c0ef3b1afadd4f0 --- /dev/null +++ b/src/components/spinner/index.ts @@ -0,0 +1,6 @@ +export { + SpinnerModule +} from "./module" +export { + SpinnerCmp +} from "./spinnerCmp/spinner.component" \ No newline at end of file diff --git a/src/components/spinner/module.ts b/src/components/spinner/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..676496b3d78b0ba6e07924d5ff702af34b4e1b58 --- /dev/null +++ b/src/components/spinner/module.ts @@ -0,0 +1,17 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SpinnerCmp } from "./spinnerCmp/spinner.component"; + +@NgModule({ + imports: [ + CommonModule + ], + declarations: [ + SpinnerCmp + ], + exports: [ + SpinnerCmp + ] +}) + +export class SpinnerModule{} \ No newline at end of file diff --git a/src/components/spinner/spinner.component.ts b/src/components/spinner/spinnerCmp/spinner.component.ts similarity index 100% rename from src/components/spinner/spinner.component.ts rename to src/components/spinner/spinnerCmp/spinner.component.ts diff --git a/src/components/spinner/spinner.template.html b/src/components/spinner/spinnerCmp/spinner.template.html similarity index 100% rename from src/components/spinner/spinner.template.html rename to src/components/spinner/spinnerCmp/spinner.template.html diff --git a/src/components/tile/tile.component.ts b/src/components/tile/tile.component.ts index fcab669508d12e06852675c41e7a20e38bc72843..80379e4b1120b6de4988f15cfbedd73c365c93a0 100644 --- a/src/components/tile/tile.component.ts +++ b/src/components/tile/tile.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, HostBinding, HostListener, Input, OnChanges, Output } from "@angular/core"; +import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, HostListener, Input, OnChanges, Output } from "@angular/core"; @Component({ selector: 'tile-cmp', @@ -6,7 +6,8 @@ import { Component, EventEmitter, HostBinding, HostListener, Input, OnChanges, O styleUrls: [ './tile.style.css' ], - exportAs: 'tileCmp' + exportAs: 'tileCmp', + changeDetection: ChangeDetectionStrategy.OnPush }) export class TileCmp implements OnChanges{ diff --git a/src/components/tile/tile.style.css b/src/components/tile/tile.style.css index e877c720820a2a9b6fbf6a0d988fad5b90385054..40d1830348c3e18559a2d2c75668e4b03cbcd3dc 100644 --- a/src/components/tile/tile.style.css +++ b/src/components/tile/tile.style.css @@ -9,6 +9,7 @@ .tile-selected { border: 2px solid #FED363; + box-sizing: border-box; } :host, @@ -17,6 +18,7 @@ img display: block; width: 100%; height: 100%; + max-width: 16rem; } img diff --git a/src/components/tile/tile.template.html b/src/components/tile/tile.template.html index 44cc134d2a135d88ad24d04c1631cb68ed827b5e..1187193efd379373fabdb461f9f4883251fb5e41 100644 --- a/src/components/tile/tile.template.html +++ b/src/components/tile/tile.template.html @@ -1,7 +1,7 @@ -<div class="position-relative"> +<div class="sxplr-position-relative"> <!-- info icon --> <div *ngIf="tileShowInfo" - class="position-absolute right-0"> + class="sxplr-position-absolute right-0"> <div mat-icon-button iav-stop="click" (click)="onInfoClick.emit($event)" @@ -9,7 +9,7 @@ backgroundColor: darktheme ? 'white' : 'black', color: darktheme ? 'black': 'white' }" - class="mat-elevation-z2 iv-custom-comp info-button"> + class="mat-elevation-z2 sxplr-custom-cmp info-button"> <small> <i class="fas fa-info"></i> </small> @@ -18,8 +18,12 @@ <!-- directory icon --> - <div *ngIf="isDir" class="position-absolute bottom-0 right-0"> - <i class="fas fa-folder folder-container fa-2x"></i> + <div *ngIf="isDir" class="sxplr-position-absolute bottom-0 right-0" + [ngClass]="{ + 'darktheme': darktheme, + 'lighttheme': !darktheme + }"> + <i class="fas fa-folder folder-container fa-2x sxplr-custom-cmp text"></i> </div> <img [src]="tileImgSrc" @@ -30,6 +34,6 @@ draggable="false"> </div> -<div class="tile-text"> +<div class="tile-text mat-body"> {{ tileText }} </div> diff --git a/src/components/tree/tree.animation.ts b/src/components/tree/tree.animation.ts deleted file mode 100644 index eca9be9c9530b480c4fdc7e696cfa252c61e2728..0000000000000000000000000000000000000000 --- a/src/components/tree/tree.animation.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { animate, state, style, transition, trigger } from "@angular/animations"; - -export const treeAnimations = trigger('collapseState', [ - state('collapsed', - style({ - 'margin-top' : '-{{ fullHeight }}px', - }), - { params : { fullHeight : 0 } }, - ), - state('visible', - style({ - 'margin-top' : '0px', - }), - { params : { fullHeight : 0 } }, - ), - transition('collapsed => visible', [ - animate('180ms'), - ]), - transition('visible => collapsed', [ - animate('180ms'), - ]), -]) diff --git a/src/components/tree/tree.component.ts b/src/components/tree/tree.component.ts deleted file mode 100644 index b462dbc2c314626f1052bc552af39ed62ae5b6db..0000000000000000000000000000000000000000 --- a/src/components/tree/tree.component.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { AfterContentChecked, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostBinding, Input, OnChanges, OnDestroy, OnInit, Optional, Output, QueryList, ViewChild, ViewChildren } from "@angular/core"; -import { Subscription } from "rxjs"; -import { ParseAttributeDirective } from "../parseAttribute.directive"; -import { treeAnimations } from "./tree.animation"; -import { TreeService } from "./treeService.service"; - -@Component({ - selector : 'tree-component', - templateUrl : './tree.template.html', - styleUrls : [ - './tree.style.css', - ], - animations : [ - treeAnimations, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) - -export class TreeComponent extends ParseAttributeDirective implements OnChanges, OnInit, OnDestroy, AfterContentChecked { - @Input() public inputItem: any = { - name : 'Untitled', - children : [], - } - @Input() public childrenExpanded: boolean = true - - @Output() public mouseentertree: EventEmitter<any> = new EventEmitter() - @Output() public mouseleavetree: EventEmitter<any> = new EventEmitter() - @Output() public mouseclicktree: EventEmitter<any> = new EventEmitter() - - @ViewChildren(TreeComponent) public treeChildren: QueryList<TreeComponent> - @ViewChild('childrenContainer', { read : ElementRef }) public childrenContainer: ElementRef - - constructor( - private cdr: ChangeDetectorRef, - @Optional() public treeService: TreeService, - ) { - super() - } - - public subscriptions: Subscription[] = [] - - public ngOnInit() { - if ( this.treeService ) { - this.subscriptions.push( - this.treeService.markForCheck.subscribe(() => this.cdr.markForCheck()), - ) - } - } - - public ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()) - } - - public _fullHeight: number = 9999 - - set fullHeight(num: number) { - this._fullHeight = num - } - - get fullHeight() { - return this._fullHeight - } - - public ngAfterContentChecked() { - this.fullHeight = this.childrenContainer ? this.childrenContainer.nativeElement.offsetHeight : 0 - this.cdr.detectChanges() - } - - public mouseenter(ev: MouseEvent) { - this.treeService.mouseenter.next({ - inputItem : this.inputItem, - node : this, - event : ev, - }) - } - - public mouseleave(ev: MouseEvent) { - this.treeService.mouseleave.next({ - inputItem : this.inputItem, - node : this, - event : ev, - }) - } - - public mouseclick(ev: MouseEvent) { - this.treeService.mouseclick.next({ - inputItem : this.inputItem, - node : this, - event : ev, - }) - } - - get chevronClass(): string { - return this.children ? - this.children.length > 0 ? - this.childrenExpanded ? - 'fa-chevron-down' : - 'fa-chevron-right' : - 'fa-none' : - 'fa-none' - } - - public handleEv(event: Event) { - event.preventDefault(); - event.stopPropagation(); - } - - public toggleChildrenShow(event: Event) { - this.childrenExpanded = !this.childrenExpanded - event.stopPropagation() - event.preventDefault() - } - - get children(): any[] { - return this.treeService ? - this.treeService.findChildren(this.inputItem) : - this.inputItem.children - } - - @HostBinding('attr.filterHidden') - get visibilityOnFilter(): boolean { - return this.treeService ? - this.treeService.searchFilter(this.inputItem) : - true - } - public handleMouseEnter(fullObj: any) { - - this.mouseentertree.emit(fullObj) - - if (this.treeService) { - this.treeService.mouseenter.next(fullObj) - } - } - - public handleMouseLeave(fullObj: any) { - - this.mouseleavetree.emit(fullObj) - - if (this.treeService) { - this.treeService.mouseleave.next(fullObj) - } - } - - public handleMouseClick(fullObj: any) { - - this.mouseclicktree.emit(fullObj) - - if (this.treeService) { - this.treeService.mouseclick.next(fullObj) - } - } - - public defaultSearchFilter = () => true -} diff --git a/src/components/tree/tree.style.css b/src/components/tree/tree.style.css deleted file mode 100644 index b05982290b10f2674d9868aa9d958274d762979d..0000000000000000000000000000000000000000 --- a/src/components/tree/tree.style.css +++ /dev/null @@ -1,111 +0,0 @@ -tree-component -{ - display:block; - margin-left:1em; -} - -div[itemContainer] -{ - display:flex; -} - -div[itemContainer] > [fas] -{ - flex: 0 0 1.2em; - align-self: center; - text-align: center; - z-index: 1; -} - -div[itemContainer] > [itemName] -{ - flex: 1 1 0px; - white-space: nowrap; -} - -div[itemContainer] > span[itemName]:hover -{ - cursor:default; - color:rgba(219, 181, 86,1); -} - -/* dashed guiding line */ - -tree-component:not(:last-child) > div[itemMasterContainer]:before -{ - - pointer-events: none; - top:-0.8em; - left:-0.5em; - height:100%; - box-sizing: border-box; - - border-left: rgba(255,255,255,1) 1px dashed; -} - -tree-component:not(:last-child) div[itemMasterContainer] > [itemContainer] -{ - position:relative; -} - - -tree-component:not(:last-child) div[itemMasterContainer] > [itemContainer]:before -{ - pointer-events: none; - content : ''; - position:absolute; - width:1.5em; - height:100%; - top:-50%; - left:-0.5em; - z-index: 0; -} - -tree-component:last-child div[itemMasterContainer] > [itemContainer] -{ - position:relative; -} - -tree-component:last-child div[itemMasterContainer] > [itemContainer]:before -{ - pointer-events: none; - content : ''; - position:absolute; - width:1.5em; - height:100%; - top:-50%; - left:-0.5em; - z-index: 0; -} - -tree-component:not(:last-child) div[itemMasterContainer]:before -{ - border-left: rgba(128,128,128,0.6) 1px dashed; -} - -tree-component:not(:last-child) div[itemMasterContainer] > [itemContainer]:before -{ - border-bottom: rgba(128,128,128,0.6) 1px dashed; -} - -tree-component div[itemMasterContainer]:last-child > [itemContainer]:before -{ - border-bottom: rgba(128,128,128,0.6) 1px dashed; - border-left : rgba(128,128,128,0.6) 1px dashed; -} - -div[itemMasterContainer] -{ - position:relative; -} -div[itemMasterContainer]:before -{ - content:' '; - position:absolute; - z-index: 0; -} - -div[childrenOverflowContainer] -{ - overflow:hidden; -} \ No newline at end of file diff --git a/src/components/tree/tree.template.html b/src/components/tree/tree.template.html deleted file mode 100644 index 10b6318bfe862cd8da5813da78aabd16e3ff6782..0000000000000000000000000000000000000000 --- a/src/components/tree/tree.template.html +++ /dev/null @@ -1,30 +0,0 @@ -<div - itemMasterContainer> - <div itemContainer> - <i - (click) = "toggleChildrenShow($event)" - [ngClass] = "chevronClass" - class = "fas" - fas> - </i> - <span - (mouseleave)="handleMouseLeave({inputItem:inputItem,node:this});handleEv($event)" - (mouseenter)="handleMouseEnter({inputItem:inputItem,node:this});handleEv($event)" - (click)="handleMouseClick({inputItem:inputItem,node:this});handleEv($event)" - [innerHTML] = "treeService ? treeService.renderNode(inputItem) : inputItem.name" - itemName> - </span> - </div> - <div childrenOverflowContainer> - <div - [@collapseState] = "{ value : childrenExpanded ? 'visible' : 'collapsed' , params : { fullHeight : fullHeight }}" - #childrenContainer> - <tree-component - *ngFor = "let child of ( treeService ? (treeService.findChildren(inputItem) | treeSearch : treeService.searchFilter : treeService.findChildren ) : inputItem.children )" - [childrenExpanded] = "childrenExpanded" - [inputItem] = "child"> - - </tree-component> - </div> - </div> -</div> \ No newline at end of file diff --git a/src/components/tree/treeBase.directive.ts b/src/components/tree/treeBase.directive.ts deleted file mode 100644 index 43fdd5954cb1b4c0145aa5a9ffb10c4d5979f46f..0000000000000000000000000000000000000000 --- a/src/components/tree/treeBase.directive.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ChangeDetectorRef, Directive, EventEmitter, Input, OnChanges, OnDestroy, Output } from "@angular/core"; -import { Subscription } from "rxjs"; -import { TreeService } from "./treeService.service"; - -@Directive({ - selector : '[treebase]', - host : { - style : ` - - `, - }, - providers : [ - TreeService, - ], -}) - -export class TreeBaseDirective implements OnDestroy, OnChanges { - @Output() public treeNodeClick: EventEmitter<any> = new EventEmitter() - @Output() public treeNodeEnter: EventEmitter<any> = new EventEmitter() - @Output() public treeNodeLeave: EventEmitter<any> = new EventEmitter() - - @Input() public renderNode: (item: any) => string = (item) => item.name - @Input() public findChildren: (item: any) => any[] = (item) => item.children - @Input() public searchFilter: (item: any) => boolean | null = () => true - - private subscriptions: Subscription[] = [] - - constructor( - public changeDetectorRef: ChangeDetectorRef, - public treeService: TreeService, - ) { - this.subscriptions.push( - this.treeService.mouseclick.subscribe((obj) => this.treeNodeClick.emit(obj)), - ) - this.subscriptions.push( - this.treeService.mouseenter.subscribe((obj) => this.treeNodeEnter.emit(obj)), - ) - this.subscriptions.push( - this.treeService.mouseleave.subscribe((obj) => this.treeNodeLeave.emit(obj)), - ) - } - - public ngOnChanges() { - this.treeService.findChildren = this.findChildren - this.treeService.renderNode = this.renderNode - this.treeService.searchFilter = this.searchFilter - - this.treeService.markForCheck.next(true) - } - - public ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()) - } -} diff --git a/src/components/tree/treeService.service.ts b/src/components/tree/treeService.service.ts deleted file mode 100644 index 2dc6bfc4dc1810038bdb008907a5d2dd5e0c27d9..0000000000000000000000000000000000000000 --- a/src/components/tree/treeService.service.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Injectable } from "@angular/core"; -import { Subject } from "rxjs"; - -@Injectable() -export class TreeService { - public mouseclick: Subject<any> = new Subject() - public mouseenter: Subject<any> = new Subject() - public mouseleave: Subject<any> = new Subject() - - public findChildren: (item: any) => any[] = (item) => item.children - public searchFilter: (item: any) => boolean | null = () => true - public renderNode: (item: any) => string = (item) => item.name - - public searchTerm: string = `` - - public markForCheck: Subject<any> = new Subject() -} diff --git a/src/components/treeSearch.pipe.spec.ts b/src/components/treeSearch.pipe.spec.ts deleted file mode 100644 index 18056277559c98ff54eff92ddc298e0528b95b54..0000000000000000000000000000000000000000 --- a/src/components/treeSearch.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* eslint-disable */ - -import { TreeSearchPipe } from './treeSearch.pipe' - -describe('TreeSearchPipe works as intended', () => { - const pipe = new TreeSearchPipe() - /* TODO add tests for tree search pipe */ -}) diff --git a/src/components/treeSearch.pipe.ts b/src/components/treeSearch.pipe.ts deleted file mode 100644 index 63c70a8e023bc4e395bf767e44428f7b9fcba308..0000000000000000000000000000000000000000 --- a/src/components/treeSearch.pipe.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; - -@Pipe({ - name : 'treeSearch', -}) - -export class TreeSearchPipe implements PipeTransform { - public transform(array: any[]|null, filterFn: (item: any) => boolean, getChildren: (item: any) => any[]): any[] { - const transformSingle = (item: any): boolean => - filterFn(item) || - (getChildren(item) - ? getChildren(item).some(transformSingle) - : false) - return array - ? array.filter(transformSingle) - : [] - } -} diff --git a/src/components/vButton/vButton.component.ts b/src/components/vButton/vButton.component.ts index f2720905b3ff3bb9ff75a5b01d9add65643b552e..3dbd98aa23dc24da26dfc564e52859cdd99ea5b7 100644 --- a/src/components/vButton/vButton.component.ts +++ b/src/components/vButton/vButton.component.ts @@ -19,7 +19,7 @@ export class IAVVerticalButton{ private color$ = new Subject<TIVBColor>() public class$ = this.color$.pipe( startWith('default'), - map(colorCls => `d-flex flex-column align-items-center iv-custom-comp ${colorCls} h-100`) + map(colorCls => `d-flex flex-column align-items-center sxplr-custom-cmp ${colorCls} h-100`) ) @Input() diff --git a/src/databrowser.fallback.ts b/src/databrowser.fallback.ts deleted file mode 100644 index 86c9c626f104954b9f28f9b3992894c6b2ea3a71..0000000000000000000000000000000000000000 --- a/src/databrowser.fallback.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { InjectionToken } from "@angular/core" -import { Observable } from "rxjs" -import { IHasId } from "./util/interfaces" - -/** - * TODO gradually move to relevant. - */ - -export const OVERRIDE_IAV_DATASET_PREVIEW_DATASET_FN = new InjectionToken<(file: any, dataset: any) => void>('OVERRIDE_IAV_DATASET_PREVIEW_DATASET_FN') -export const GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME = new InjectionToken<({ datasetSchema, datasetId, filename }) => Observable<any|null>>('GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME') -export const kgTos = `The interactive viewer queries HBP Knowledge Graph Data Platform ("KG") for published datasets. - - -Access to the data and metadata provided through KG requires that you cite and acknowledge said data and metadata according to the Terms and Conditions of the Platform. - - -Citation requirements are outlined <https://www.humanbrainproject.eu/en/explore-the-brain/search-terms-of-use#citations> . - - -Acknowledgement requirements are outlined <https://www.humanbrainproject.eu/en/explore-the-brain/search-terms-of-use#acknowledgements> - - -These outlines are based on the authoritative Terms and Conditions are found <https://www.humanbrainproject.eu/en/explore-the-brain/search-terms-of-use> - - -If you do not accept the Terms & Conditions you are not permitted to access or use the KG to search for, to submit, to post, or to download any materials found there-in. -` - -export function getKgSchemaIdFromFullId(fullId: string): [string, string]{ - if (!fullId) { return [null, null] } - const match = /([\w\-.]*\/[\w\-.]*\/[\w\-.]*\/[\w\-.]*)\/([\w\-.]*)$/.exec(fullId) - if (!match) { return [null, null] } - return [match[1], match[2]] -} - - -export interface IKgReferenceSpace { - name: string -} - -export interface IKgPublication { - name: string - doi: string - cite: string -} - -export interface IKgParcellationRegion { - id?: string - name: string -} - -export interface IKgActivity { - methods: string[] - preparation: string[] - protocols: string[] -} - -export interface IKgDataEntry { - activity: IKgActivity[] - name: string - description: string - license: string[] - licenseInfo: string[] - parcellationRegion: IKgParcellationRegion[] - formats: string[] - custodians: string[] - contributors: string[] - referenceSpaces: IKgReferenceSpace[] - files: File[] - publications: IKgPublication[] - embargoStatus: IHasId[] - - methods: string[] - protocols: string[] - - preview?: boolean - - /** - * TODO typo, should be kgReferences - */ - kgReference: string[] - - id: string - fullId: string -} - -export type TypePreviewDispalyed = (file, dataset) => Observable<boolean> -export const IAV_DATASET_PREVIEW_ACTIVE = new InjectionToken<TypePreviewDispalyed>('IAV_DATASET_PREVIEW_ACTIVE') - - -export enum EnumPreviewFileTypes{ - NIFTI, - IMAGE, - CHART, - OTHER, - VOLUMES, -} - -export interface DatasetPreview { - datasetId: string - filename: string -} - -export function determinePreviewFileType(previewFile: any): EnumPreviewFileTypes { - if (!previewFile) throw new Error(`previewFile is required to determine the file type`) - const { mimetype, data } = previewFile - const chartType = data && data['chart.js'] && data['chart.js'].type - const registerdVolumes = data && data['iav-registered-volumes'] - if ( mimetype === 'application/nifti' ) { return EnumPreviewFileTypes.NIFTI } - if ( /^image/.test(mimetype)) { return EnumPreviewFileTypes.IMAGE } - if ( /application\/json/.test(mimetype) && (chartType === 'line' || chartType === 'radar')) { return EnumPreviewFileTypes.CHART } - if ( /application\/json/.test(mimetype) && !!registerdVolumes) { return EnumPreviewFileTypes.VOLUMES } - return EnumPreviewFileTypes.OTHER -} diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts index 83bad8bcb9a8fe4ec1e40eb4e419d41dc7748294..f4d5c96275db63b6e84e76a7223c597139fba508 100644 --- a/src/environments/environment.common.ts +++ b/src/environments/environment.common.ts @@ -4,8 +4,7 @@ export const environment = { VERSION: 'unknown version', PRODUCTION: true, BACKEND_URL: null, - DATASET_PREVIEW_URL: 'https://hbp-kg-dataset-previewer.apps.hbp.eu/v2', - BS_REST_URL: 'https://siibra-api-rc.apps.hbp.eu/v1_0', + BS_REST_URL: 'https://siibra-api-latest.apps-dev.hbp.eu/v2_0', SPATIAL_TRANSFORM_BACKEND: 'https://hbp-spatial-backend.apps.hbp.eu', MATOMO_URL: null, MATOMO_ID: null, diff --git a/src/environments/parseEnv.js b/src/environments/parseEnv.js index dbd985314dc0c472611870143293d584b95e1e11..b05909d596c85cc1875650602cafb8a5fa2fba3a 100644 --- a/src/environments/parseEnv.js +++ b/src/environments/parseEnv.js @@ -7,7 +7,6 @@ const main = async () => { const pathToEnvFile = path.join(__dirname, './environment.prod.ts') const { BACKEND_URL, - DATASET_PREVIEW_URL, STRICT_LOCAL, MATOMO_URL, MATOMO_ID, @@ -19,7 +18,6 @@ const main = async () => { console.log(`[parseEnv.js] parse envvar:`, { BACKEND_URL, - DATASET_PREVIEW_URL, STRICT_LOCAL, MATOMO_URL, MATOMO_ID, @@ -43,7 +41,6 @@ export const environment = { VERSION: ${version}, BS_REST_URL: ${JSON.stringify(BS_REST_URL)}, BACKEND_URL: ${JSON.stringify(BACKEND_URL)}, - DATASET_PREVIEW_URL: ${JSON.stringify(DATASET_PREVIEW_URL)}, STRICT_LOCAL: ${JSON.stringify(STRICT_LOCAL)}, MATOMO_URL: ${JSON.stringify(MATOMO_URL)}, MATOMO_ID: ${JSON.stringify(MATOMO_ID)}, diff --git a/src/extra_styles.css b/src/extra_styles.css index 928bcf43c3700cef7c1b006cc7b85ae1bedda609..35a70999b7b44433ec39f25835dac0ceb5bff1a7 100644 --- a/src/extra_styles.css +++ b/src/extra_styles.css @@ -293,6 +293,11 @@ markdown-dom p transform: rotate(270deg)!important; } +.ws-normal +{ + white-space: normal!important; +} + .ws-no-wrap { white-space: nowrap!important; @@ -579,61 +584,11 @@ markdown-dom p flex-grow:1; } -.transform-origin-left-center -{ - transform-origin: 0% 50%; -} - -.transform-origin-center -{ - transform-origin: 50% 50%; -} - .box-shadow-none { box-shadow: none!important; } -.translate-x-2 -{ - transform: translateX(1em); -} - -.translate-x-3 -{ - transform: translateX(1.5em); -} - -.translate-x-4 -{ - transform: translateX(2em); -} - -.translate-x-6 -{ - transform: translateX(3em); -} - -.translate-x-4-n -{ - transform: translateX(-2em); -} - -.translate-x-6-n -{ - transform: translateX(-3em); -} - -.translate-x-7-n -{ - transform: translateX(-3.5em); -} - -.translate-x-8-n -{ - transform: translateX(-4em); -} - /* this is required to physically link label with side bar */ .mat-drawer-content-overflow-visible > mat-drawer-content, /* this is required to show the popout info of template and parcellation */ @@ -642,11 +597,6 @@ markdown-dom p overflow: visible!important; } -.overflow-visible -{ - overflow: visible!important; -} - mat-icon[fontset="fas"], mat-icon[fontset="far"] { @@ -751,27 +701,11 @@ kg-dataset-previewer > img width: 100%; } -.hover-grab -{ - opacity: 0.5; - transition: opacity 200ms ease-in-out; - cursor: move; -} - -.hover-grab:hover -{ - opacity: 1.0; -} - .m-15 { margin: 15px; } -.m-1px -{ - margin:1px; -} .ml-15px-n { @@ -873,10 +807,6 @@ mat-list.sm mat-list-item { height:50px; } -.scale-80 -{ - transform: scale(0.8); -} .grid { @@ -904,4 +834,50 @@ mat-list.sm mat-list-item .v-align-top { vertical-align: top; -} \ No newline at end of file +} + +.text-overflow-ellipses +{ + text-overflow: ellipsis; +} + +iav-cmp-viewer-container .mat-chip-list-wrapper +{ + flex-wrap: nowrap; +} + +sxplr-sapiviews-features-ieeg-ieegdataset .mat-chip-list-wrapper +{ + overflow-y: hidden; + overflow-x: auto; +} + +iav-cmp-viewer-container poly-update-cmp .mat-chip-list-wrapper +{ + flex-wrap: wrap; +} + +quick-tour-unit svg +{ + pointer-events: none; + stroke-width: 3px; + stroke: rgb(255, 255, 255); + stroke-linecap: round; + stroke-linejoin: round; +} + +sxplr-sapiviews-features-receptor-autoradiograph canvas +{ + max-width: 100%; + max-height: 100%; +} + +how-to-cite img +{ + width: 100%; +} + +.mat-menu-panel.parc-smart-chip-menu-panel +{ + max-width: 100vw; +} diff --git a/src/getFileInput/fileInputModal/fileInputModal.template.html b/src/getFileInput/fileInputModal/fileInputModal.template.html index 4fdc365d5169260206961acf60c0e117f29ce582..b491fb597fcda41d3b63c39881e7262b896efb89 100644 --- a/src/getFileInput/fileInputModal/fileInputModal.template.html +++ b/src/getFileInput/fileInputModal/fileInputModal.template.html @@ -34,7 +34,7 @@ </label> <input (change)="handleFileInputChange($event)" type="file" - class="position-absolute left-0 top-0 w-0 h-0 invisible" + class="position-absolute left-0 tosxplr-p-0 w-0 h-0 invisible" name="file-input" id="file-input" #fileInput> diff --git a/src/glue.spec.ts b/src/glue.spec.ts index 63009e7a8914a09f7bf31672669438cce70cb4f2..e398540319d21c6cf282f31bbe73359a39e760c6 100644 --- a/src/glue.spec.ts +++ b/src/glue.spec.ts @@ -1,16 +1,5 @@ -import { TestBed, tick, fakeAsync, discardPeriodicTasks } from "@angular/core/testing" -import { DatasetPreviewGlue, glueSelectorGetUiStatePreviewingFiles, glueActionRemoveDatasetPreview, datasetPreviewMetaReducer, glueActionAddDatasetPreview, GlueEffects, ClickInterceptorService } from "./glue" -import { provideMockStore, MockStore } from "@ngrx/store/testing" -import { getRandomHex, getIdObj } from 'common/util' -import { EnumWidgetTypes, TypeOpenedWidget, uiActionSetPreviewingDatasetFiles, uiStatePreviewingDatasetFilesSelector } from "./services/state/uiState.store.helper" -import { hot } from "jasmine-marbles" -import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing" -import { glueActionToggleDatasetPreview } from './glue' -import { DS_PREVIEW_URL } from 'src/util/constants' -import { NgLayersService } from "./ui/layerbrowser/ngLayerService.service" -import { GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME } from "./databrowser.fallback" -import { viewerStateSelectedTemplateSelector } from "./services/state/viewerState/selectors" -import { generalActionError } from "./services/stateStore.helper" +import { ClickInterceptorService } from "./glue" +import { getRandomHex } from 'common/util' const mockActionOnSpyReturnVal0 = { id: getRandomHex(), @@ -91,587 +80,6 @@ const dataset1 = { } describe('> glue.ts', () => { - describe('> DatasetPreviewGlue', () => { - - const initialState = { - uiState: { - previewingDatasetFiles: [] - }, - viewerState: { - regionsSelected: [] - } - } - beforeEach(() => { - actionOnWidgetSpy = jasmine.createSpy('actionOnWidget').and.returnValues( - mockActionOnSpyReturnVal0, - mockActionOnSpyReturnVal1 - ) - - TestBed.configureTestingModule({ - imports: [ - HttpClientTestingModule, - ], - providers: [ - DatasetPreviewGlue, - provideMockStore({ - initialState - }), - NgLayersService - ] - }) - }) - - afterEach(() => { - actionOnWidgetSpy.calls.reset() - const ctrl = TestBed.inject(HttpTestingController) - ctrl.verify() - }) - - describe('> #datasetPreviewDisplayed', () => { - - it('> correctly emits true when store changes', () => { - const glue = TestBed.inject(DatasetPreviewGlue) - const store = TestBed.inject(MockStore) - - const obs = glue.datasetPreviewDisplayed(file1) - - store.setState({ - uiState: { - previewingDatasetFiles: [] - } - }) - const uiStateSelector = store.overrideSelector( - glueSelectorGetUiStatePreviewingFiles, - [] - ) - - uiStateSelector.setResult([ file1 ] ) - store.refreshState() - expect(obs).toBeObservable( - hot('a', { - a: true, - b: false - }) - ) - }) - - - it('> correctly emits false when store changes', () => { - const store = TestBed.inject(MockStore) - - const glue = TestBed.inject(DatasetPreviewGlue) - store.setState({ - uiState: { - previewingDatasetFiles: [ file2 ] - } - }) - const obs = glue.datasetPreviewDisplayed(file1) - store.refreshState() - - expect(obs).toBeObservable( - hot('b', { - a: true, - b: false - }) - ) - }) - }) - - describe('> #displayDatasetPreview', () => { - - it('> calls dispatch', () => { - - const glue = TestBed.inject(DatasetPreviewGlue) - const mockStore = TestBed.inject(MockStore) - const dispatchSpy = spyOn(mockStore, 'dispatch').and.callThrough() - - glue.displayDatasetPreview(file1, dataset1 as any) - - expect(dispatchSpy).toHaveBeenCalled() - }) - - it('> dispatches glueActionToggleDatasetPreview with the correct filename', () => { - - const glue = TestBed.inject(DatasetPreviewGlue) - const mockStore = TestBed.inject(MockStore) - const dispatchSpy = spyOn(mockStore, 'dispatch').and.callThrough() - - glue.displayDatasetPreview(file1, dataset1 as any) - - const args = dispatchSpy.calls.allArgs() - const [ action ] = args[0] - - expect(action.type).toEqual(glueActionToggleDatasetPreview.type) - expect((action as any).datasetPreviewFile.filename).toEqual(file1.filename) - }) - - it('> uses datasetId of file if present', () => { - - const glue = TestBed.inject(DatasetPreviewGlue) - const mockStore = TestBed.inject(MockStore) - const dispatchSpy = spyOn(mockStore, 'dispatch').and.callThrough() - - glue.displayDatasetPreview(file1, dataset1 as any) - - const args = dispatchSpy.calls.allArgs() - const [ action ] = args[0] - - expect((action as any).datasetPreviewFile.datasetId).toEqual(file1.datasetId) - }) - - it('> falls back to dataset fullId if datasetId not present on file', () => { - - const glue = TestBed.inject(DatasetPreviewGlue) - const mockStore = TestBed.inject(MockStore) - const dispatchSpy = spyOn(mockStore, 'dispatch').and.callThrough() - - const { datasetId, ...noDsIdFile1 } = file1 - glue.displayDatasetPreview(noDsIdFile1 as any, dataset1 as any) - - const { fullId } = dataset1 - const { kgId } = getIdObj(fullId) - - const args = dispatchSpy.calls.allArgs() - const [ action ] = args[0] - - expect((action as any).datasetPreviewFile.datasetId).toEqual(kgId) - }) - }) - - describe('> http interceptor', () => { - it('> on no state, does not call', fakeAsync(() => { - - const store = TestBed.inject(MockStore) - const ctrl = TestBed.inject(HttpTestingController) - const glue = TestBed.inject(DatasetPreviewGlue) - - store.setState({ - uiState: { - previewingDatasetFiles: [] - } - }) - - const { datasetId, filename } = file1 - // debounce at 100ms - tick(200) - ctrl.expectNone({}) - - discardPeriodicTasks() - })) - it('> on set state, calls end point to fetch full data', fakeAsync(() => { - - const store = TestBed.inject(MockStore) - const ctrl = TestBed.inject(HttpTestingController) - const glue = TestBed.inject(DatasetPreviewGlue) - - store.setState({ - uiState: { - previewingDatasetFiles: [ file1 ] - } - }) - - const { datasetId, filename } = file1 - // debounce at 100ms - tick(200) - - const req = ctrl.expectOne(`${DS_PREVIEW_URL}/${encodeURIComponent('minds/core/dataset/v1.0.0')}/${datasetId}/${encodeURIComponent(filename)}`) - req.flush(nifti) - discardPeriodicTasks() - })) - - it('> if returns 404, should be handled gracefully', fakeAsync(() => { - - const ctrl = TestBed.inject(HttpTestingController) - const glue = TestBed.inject(DatasetPreviewGlue) - - const { datasetId, filename } = file3 - - const obs$ = glue.getDatasetPreviewFromId({ datasetId, filename }) - let expectedVal = 'defined' - obs$.subscribe(val => expectedVal = val) - tick(200) - - const req = ctrl.expectOne(`${DS_PREVIEW_URL}/${encodeURIComponent('minds/core/dataset/v1.0.0')}/${encodeURIComponent(datasetId)}/${encodeURIComponent(filename)}`) - req.flush(null, { status: 404, statusText: 'Not found' }) - - expect(expectedVal).toBeNull() - discardPeriodicTasks() - })) - }) - }) - - - describe('> datasetPreviewMetaReducer', () => { - - const obj1: TypeOpenedWidget = { - type: EnumWidgetTypes.DATASET_PREVIEW, - data: file1 - } - - const stateEmpty = { - uiState: { - previewingDatasetFiles: [] - } - } as { uiState: { previewingDatasetFiles: {datasetId: string, filename: string}[] } } - - const stateObj1 = { - uiState: { - previewingDatasetFiles: [ file1 ] - } - } as { uiState: { previewingDatasetFiles: {datasetId: string, filename: string}[] } } - - const reducer = jasmine.createSpy('reducer') - const metaReducer = datasetPreviewMetaReducer(reducer) - - afterEach(() => { - reducer.calls.reset() - }) - describe('> on glueActionAddDatasetPreview', () => { - describe('> if preview does not yet exist in state', () => { - beforeEach(() => { - metaReducer(stateEmpty, glueActionAddDatasetPreview({ datasetPreviewFile: file1 })) - }) - - it('> expect reducer to be called once', () => { - expect(reducer).toHaveBeenCalled() - expect(reducer.calls.count()).toEqual(1) - }) - - it('> expect call sig of reducer call to be correct', () => { - - const [ args ] = reducer.calls.allArgs() - expect(args[0]).toEqual(stateEmpty) - expect(args[1].type).toEqual(uiActionSetPreviewingDatasetFiles.type) - expect(args[1].previewingDatasetFiles).toEqual([ file1 ]) - }) - }) - - describe('> if preview already exist in state', () => { - beforeEach(() => { - metaReducer(stateObj1, glueActionAddDatasetPreview({ datasetPreviewFile: file1 })) - }) - it('> should still call reducer', () => { - expect(reducer).toHaveBeenCalled() - expect(reducer.calls.count()).toEqual(1) - }) - - it('> there should now be two previews in dispatched action', () => { - - const [ args ] = reducer.calls.allArgs() - expect(args[0]).toEqual(stateObj1) - expect(args[1].type).toEqual(uiActionSetPreviewingDatasetFiles.type) - expect(args[1].previewingDatasetFiles).toEqual([ file1, file1 ]) - }) - }) - }) - describe('> on glueActionRemoveDatasetPreview', () => { - it('> removes id as expected', () => { - metaReducer(stateObj1, glueActionRemoveDatasetPreview({ datasetPreviewFile: file1 })) - expect(reducer).toHaveBeenCalled() - expect(reducer.calls.count()).toEqual(1) - const [ args ] = reducer.calls.allArgs() - expect(args[0]).toEqual(stateObj1) - expect(args[1].type).toEqual(uiActionSetPreviewingDatasetFiles.type) - expect(args[1].previewingDatasetFiles).toEqual([ ]) - }) - }) - }) - - describe('> GlueEffects', () => { - - /** - * related to previews - */ - const mockTemplate = { - fullId: 'bar' - } - const mockPreviewFileIds = { - datasetId: 'foo', - filename: 'bar' - } - const mockPreviewFileIds2 = { - datasetId: 'foo2', - filename: 'bar2' - } - const mockPreviewFileIds3 = { - datasetId: 'foo3', - filename: 'bar3' - } - const mockPreviewFileIds4 = { - datasetId: 'foo4', - filename: 'bar4' - } - const previewFileNoRefSpace = { - name: 'bla bla 4', - datasetId: 'foo4', - filename: 'bar4' - } - const fittingMockPreviewFile = { - name: 'bla bla2', - datasetId: 'foo2', - filename: 'bar2', - referenceSpaces: [{ - fullId: 'bar' - }] - } - const mockPreviewFile = { - name: 'bla bla', - datasetId: 'foo', - filename: 'bar', - referenceSpaces: [{ - fullId: 'hello world' - }] - } - - const defaultState = { - viewerState: { - templateSelected: null, - parcellationSelected: null, - regionsSelected: [] - }, - uiState: { - previewingDatasetFiles: [] - } - } - - const mockGetDatasetPreviewFromId = jasmine.createSpy('getDatasetPreviewFromId') - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - GlueEffects, - provideMockStore({ - initialState: defaultState - }), - { - provide: GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME, - useValue: mockGetDatasetPreviewFromId - } - ] - }) - mockGetDatasetPreviewFromId.withArgs(mockPreviewFileIds2).and.returnValue( - hot('(a|)', { - a: fittingMockPreviewFile - }) - ) - mockGetDatasetPreviewFromId.withArgs({ datasetId: 'foo', filename: 'bar' }).and.returnValue( - hot('(a|)', { - a: mockPreviewFile - }) - ) - mockGetDatasetPreviewFromId.withArgs(mockPreviewFileIds3).and.returnValue( - hot('(a|)', { - a: null - }) - ) - mockGetDatasetPreviewFromId.withArgs(mockPreviewFileIds4).and.returnValue( - hot('(a|)', { - a: previewFileNoRefSpace - }) - ) - }) - - afterEach(() => { - mockGetDatasetPreviewFromId.calls.reset() - }) - - describe('> regionTemplateParcChange$', () => { - - const copiedState0 = JSON.parse(JSON.stringify(defaultState)) - copiedState0.viewerState.regionsSelected = [{ name: 'coffee' }] - copiedState0.viewerState.parcellationSelected = { name: 'chicken' } - copiedState0.viewerState.templateSelected = { name: 'spinach' } - - const generateTest = (m1, m2) => { - - const mockStore = TestBed.inject(MockStore) - mockStore.setState(copiedState0) - const glueEffects = TestBed.inject(GlueEffects) - /** - * couldn't get jasmine-marble to coopoerate - * TODO test proper with jasmine marble (?) - */ - let numOfEmit = 0 - const sub = glueEffects.regionTemplateParcChange$.subscribe(() => { - numOfEmit += 1 - }) - - const copiedState1 = JSON.parse(JSON.stringify(copiedState0)) - m1(copiedState1) - mockStore.setState(copiedState1) - expect(numOfEmit).toEqual(1) - - const copiedState2 = JSON.parse(JSON.stringify(copiedState0)) - m2(copiedState2) - mockStore.setState(copiedState2) - expect(numOfEmit).toEqual(2) - - sub.unsubscribe() - } - - it('> on change of region, should emit', () => { - generateTest( - copiedState1 => copiedState1.viewerState.regionsSelected = [{ name: 'cake' }], - copiedState2 => copiedState2.viewerState.regionsSelected = [{ name: 't bone' }] - ) - }) - - it('> on change of parcellation, should emit', () => { - generateTest( - copiedState1 => copiedState1.viewerState.parcellationSelected = { name: 'pizza' }, - copiedState2 => copiedState2.viewerState.parcellationSelected = { name: 'pizza on pineapple' } - ) - }) - - it('> on change of template, should emit', () => { - generateTest( - copiedState1 => copiedState1.viewerState.templateSelected = { name: 'calzone' }, - copiedState2 => copiedState2.viewerState.templateSelected = { name: 'calzone on pineapple' } - ) - }) - }) - - - describe('> unsuitablePreviews$', () => { - - it('> calls injected getDatasetPreviewFromId', () => { - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, mockTemplate) - mockStore.overrideSelector(uiStatePreviewingDatasetFilesSelector, [mockPreviewFileIds2]) - - const glueEffects = TestBed.inject(GlueEffects) - expect(glueEffects.unsuitablePreviews$).toBeObservable( - hot('') - ) - /** - * calling twice, once to check if the dataset preview can be retrieved, the other to check the referenceSpace - */ - expect(mockGetDatasetPreviewFromId).toHaveBeenCalledTimes(2) - expect(mockGetDatasetPreviewFromId).toHaveBeenCalledWith(mockPreviewFileIds2) - }) - - it('> if getDatasetPreviewFromId throws in event stream, handles gracefully', () => { - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, mockTemplate) - mockStore.overrideSelector(uiStatePreviewingDatasetFilesSelector, [mockPreviewFileIds3]) - - const glueEffects = TestBed.inject(GlueEffects) - - expect(glueEffects.unsuitablePreviews$).toBeObservable( - hot('a', { - a: [ mockPreviewFileIds3 ] - }) - ) - }) - - describe('> filtering out dataset previews that do not satisfy reference space requirements', () => { - it('> if reference spaces does not match the selected reference template, will emit', () => { - const mockStore = TestBed.inject(MockStore) - - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, mockTemplate) - mockStore.overrideSelector(uiStatePreviewingDatasetFilesSelector, [mockPreviewFileIds]) - const glueEffects = TestBed.inject(GlueEffects) - expect(glueEffects.unsuitablePreviews$).toBeObservable( - hot('a', { - a: [ mockPreviewFile ] - }) - ) - }) - }) - - describe('> keeping dataset previews that satisfy reference space criteria', () => { - it('> if ref space is undefined, keep preview', () => { - - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, mockTemplate) - mockStore.overrideSelector(uiStatePreviewingDatasetFilesSelector, [mockPreviewFileIds4]) - const glueEffects = TestBed.inject(GlueEffects) - expect(glueEffects.unsuitablePreviews$).toBeObservable( - hot('') - ) - }) - - it('> if ref space is defined, and matches, keep preview', () => { - - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, mockTemplate) - mockStore.overrideSelector(uiStatePreviewingDatasetFilesSelector, [mockPreviewFileIds2]) - const glueEffects = TestBed.inject(GlueEffects) - expect(glueEffects.unsuitablePreviews$).toBeObservable( - hot('') - ) - }) - }) - - }) - - describe('> uiRemoveUnsuitablePreviews$', () => { - it('> emits whenever unsuitablePreviews$ emits', () => { - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, mockTemplate) - mockStore.overrideSelector(uiStatePreviewingDatasetFilesSelector, [mockPreviewFileIds]) - const glueEffects = TestBed.inject(GlueEffects) - expect(glueEffects.uiRemoveUnsuitablePreviews$).toBeObservable( - hot('a', { - a: generalActionError({ - message: `Dataset previews ${mockPreviewFile.name} cannot be displayed.` - }) - }) - ) - }) - }) - - describe('> filterDatasetPreviewByTemplateSelected$', () => { - - it('> remove 1 preview datasetfile depending on unsuitablepreview$', () => { - const mockStore = TestBed.inject(MockStore) - - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, mockTemplate) - mockStore.overrideSelector(uiStatePreviewingDatasetFilesSelector, [mockPreviewFileIds]) - const glueEffects = TestBed.inject(GlueEffects) - expect(glueEffects.filterDatasetPreviewByTemplateSelected$).toBeObservable( - hot('a', { - a: uiActionSetPreviewingDatasetFiles({ - previewingDatasetFiles: [ ] - }) - }) - ) - - }) - it('> remove 1 preview datasetfile (get preview info fail) depending on unsuitablepreview$', () => { - const mockStore = TestBed.inject(MockStore) - - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, mockTemplate) - mockStore.overrideSelector(uiStatePreviewingDatasetFilesSelector, [mockPreviewFileIds3]) - const glueEffects = TestBed.inject(GlueEffects) - expect(glueEffects.filterDatasetPreviewByTemplateSelected$).toBeObservable( - hot('a', { - a: uiActionSetPreviewingDatasetFiles({ - previewingDatasetFiles: [ ] - }) - }) - ) - - }) - it('> remove 2 preview datasetfile depending on unsuitablepreview$', () => { - const mockStore = TestBed.inject(MockStore) - - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, mockTemplate) - mockStore.overrideSelector(uiStatePreviewingDatasetFilesSelector, [mockPreviewFileIds, mockPreviewFileIds2, mockPreviewFileIds4]) - const glueEffects = TestBed.inject(GlueEffects) - expect(glueEffects.filterDatasetPreviewByTemplateSelected$).toBeObservable( - hot('a', { - a: uiActionSetPreviewingDatasetFiles({ - previewingDatasetFiles: [ mockPreviewFileIds2, mockPreviewFileIds4 ] - }) - }) - ) - - }) - - }) - - }) describe('> ClickInterceptorService', () => { /** diff --git a/src/glue.ts b/src/glue.ts index 027858745e6ac5223431e586aa6bd016e1ff3611..89b97a7f5647a8ff7e99a7bcf1dfa52837ab9c85 100644 --- a/src/glue.ts +++ b/src/glue.ts @@ -1,447 +1,6 @@ -import { uiActionSetPreviewingDatasetFiles, IDatasetPreviewData, uiStatePreviewingDatasetFilesSelector } from "./services/state/uiState.store.helper" -import { OnDestroy, Injectable, Inject, InjectionToken } from "@angular/core" -import { DatasetPreview, determinePreviewFileType, EnumPreviewFileTypes, IKgDataEntry, getKgSchemaIdFromFullId, GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME } from "./databrowser.fallback" -import { Subscription, Observable, forkJoin, of, merge, combineLatest } from "rxjs" -import { select, Store, ActionReducer, createAction, props, createSelector, Action } from "@ngrx/store" -import { startWith, map, shareReplay, pairwise, debounceTime, distinctUntilChanged, tap, switchMap, withLatestFrom, mapTo, switchMapTo, filter, skip, catchError } from "rxjs/operators" -import { getIdObj } from 'common/util' -import { MatDialogRef } from "@angular/material/dialog" -import { HttpClient } from "@angular/common/http" -import { DS_PREVIEW_URL } from 'src/util/constants' -import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer } from "./services/state/ngViewerState.store.helper" -import { ARIA_LABELS } from 'common/constants' -import { Effect } from "@ngrx/effects" -import { viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector, viewerStateSelectedParcellationSelector } from "./services/state/viewerState/selectors" -import { ngViewerActionClearView } from './services/state/ngViewerState/actions' -import { generalActionError } from "./services/stateStore.helper" +import { Injectable } from "@angular/core" import { RegDeregController } from "./util/regDereg.base" -const prvFilterNull = ({ prvToDismiss, prvToShow }) => ({ - prvToDismiss: prvToDismiss.filter(v => !!v), - prvToShow: prvToShow.filter(v => !!v), -}) - -export const glueActionToggleDatasetPreview = createAction( - '[glue] toggleDatasetPreview', - props<{ datasetPreviewFile: IDatasetPreviewData }>() -) - -export const glueActionAddDatasetPreview = createAction( - '[glue] addDatasetPreview', - props<{ datasetPreviewFile: IDatasetPreviewData }>() -) - -export const glueActionRemoveDatasetPreview = createAction( - '[glue] removeDatasetPreview', - props<{ datasetPreviewFile: IDatasetPreviewData }>() -) - -export const glueSelectorGetUiStatePreviewingFiles = createSelector( - (state: any) => state.uiState, - uiState => uiState.previewingDatasetFiles -) - -export interface IDatasetPreviewGlue{ - datasetPreviewDisplayed(file: DatasetPreview, dataset: IKgDataEntry): Observable<boolean> - displayDatasetPreview(previewFile: DatasetPreview, dataset: IKgDataEntry): void -} - -@Injectable({ - providedIn: 'root' -}) - -export class GlueEffects { - - public regionTemplateParcChange$ = merge( - this.store$.pipe( - select(viewerStateSelectedRegionsSelector), - map(rs => (rs || []).map(r => r['name']).sort().join(',')), - distinctUntilChanged(), - skip(1), - ), - this.store$.pipe( - select(viewerStateSelectedTemplateSelector), - map(tmpl => tmpl - ? tmpl['@id'] || tmpl['name'] - : null), - distinctUntilChanged(), - skip(1) - ), - this.store$.pipe( - select(viewerStateSelectedParcellationSelector), - map(parc => parc - ? parc['@id'] || parc['name'] - : null), - distinctUntilChanged(), - skip(1) - ) - ).pipe( - mapTo(true) - ) - - @Effect() - resetDatasetPreview$: Observable<any> = this.store$.pipe( - select(uiStatePreviewingDatasetFilesSelector), - distinctUntilChanged(), - filter(previews => previews?.length > 0), - switchMapTo(this.regionTemplateParcChange$) - ).pipe( - mapTo(uiActionSetPreviewingDatasetFiles({ - previewingDatasetFiles: [] - })) - ) - - unsuitablePreviews$: Observable<any> = merge( - /** - * filter out the dataset previews, whose details cannot be fetchd from getdatasetPreviewFromId method - */ - - this.store$.pipe( - select(uiStatePreviewingDatasetFilesSelector), - switchMap(previews => - forkJoin( - previews.map(prev => this.getDatasetPreviewFromId(prev).pipe( - // filter out the null's - filter(val => !val), - mapTo(prev) - )) - ).pipe( - filter(previewFiles => previewFiles.length > 0) - ) - ) - ), - /** - * filter out the dataset previews, whose details can be fetched from getDatasetPreviewFromId method - */ - combineLatest([ - this.store$.pipe( - select(viewerStateSelectedTemplateSelector) - ), - this.store$.pipe( - select(uiStatePreviewingDatasetFilesSelector), - switchMap(previews => - forkJoin( - previews.map(prev => this.getDatasetPreviewFromId(prev).pipe( - filter(val => !!val) - )) - ).pipe( - // filter out the null's - filter(previewFiles => previewFiles.length > 0) - ) - ), - ) - ]).pipe( - map(([ templateSelected, previewFiles ]) => - previewFiles.filter(({ referenceSpaces }) => - // if referenceSpaces of the dataset preview is undefined, assume it is suitable for all reference spaces - (!referenceSpaces) - ? false - : !referenceSpaces.some(({ fullId }) => fullId === '*' || fullId === templateSelected.fullId) - ) - ), - ) - ).pipe( - filter(arr => arr.length > 0), - shareReplay(1), - ) - - @Effect() - uiRemoveUnsuitablePreviews$: Observable<any> = this.unsuitablePreviews$.pipe( - map(previews => generalActionError({ - message: `Dataset previews ${previews.map(v => v.name)} cannot be displayed.` - })) - ) - - @Effect() - filterDatasetPreviewByTemplateSelected$: Observable<any> = this.unsuitablePreviews$.pipe( - withLatestFrom( - this.store$.pipe( - select(uiStatePreviewingDatasetFilesSelector), - ) - ), - map(([ unsuitablePreviews, previewFiles ]) => uiActionSetPreviewingDatasetFiles({ - previewingDatasetFiles: previewFiles.filter( - ({ datasetId: dsId, filename: fName }) => !unsuitablePreviews.some( - ({ datasetId, filename }) => datasetId === dsId && fName === filename - ) - ) - })) - ) - - @Effect() - resetConnectivityMode: Observable<any> = this.store$.pipe( - select(viewerStateSelectedRegionsSelector), - pairwise(), - filter(([o, n]) => o.length > 0 && n.length === 0), - mapTo( - ngViewerActionClearView({ - payload: { - 'Connectivity': false - } - }) - ) - ) - - constructor( - private store$: Store<any>, - @Inject(GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME) private getDatasetPreviewFromId: (arg) => Observable<any|null> - ){ - } -} - -@Injectable({ - providedIn: 'root' -}) - -export class DatasetPreviewGlue implements IDatasetPreviewGlue, OnDestroy{ - - static readonly DEFAULT_DIALOG_OPTION = { - ariaLabel: ARIA_LABELS.DATASET_FILE_PREVIEW, - hasBackdrop: false, - disableClose: true, - autoFocus: false, - panelClass: 'mat-card-sm', - height: '50vh', - width: '350px', - position: { - left: '5px' - }, - } - - static GetDatasetPreviewId(data: IDatasetPreviewData ){ - const { datasetSchema = 'minds/core/dataset/v1.0.0', datasetId, filename } = data - return `${datasetSchema}/${datasetId}:${filename}` - } - - static GetDatasetPreviewFromId(id: string): IDatasetPreviewData{ - const re = /([a-f0-9-]+):(.+)$/.exec(id) - if (!re) throw new Error(`id cannot be decoded: ${id}`) - return { datasetId: re[1], filename: re[2] } - } - - static PreviewFileIsInCorrectSpace(previewFile, templateSelected): boolean{ - - const re = getKgSchemaIdFromFullId( - (templateSelected && templateSelected.fullId) || '' - ) - const templateId = re && re[0] && `${re[0]}/${re[1]}` - const { referenceSpaces } = previewFile - return referenceSpaces.findIndex(({ fullId }) => fullId === '*' || fullId === templateId) >= 0 - } - - private subscriptions: Subscription[] = [] - private openedPreviewMap = new Map<string, {id: string, matDialogRef: MatDialogRef<any>}>() - - private previewingDatasetFiles$: Observable<IDatasetPreviewData[]> = this.store$.pipe( - select(glueSelectorGetUiStatePreviewingFiles), - startWith([]), - shareReplay(1), - ) - - public _volumePreview$ = this.previewingDatasetFiles$.pipe( - switchMap(arr => arr.length > 0 - ? forkJoin(arr.map(v => this.getDatasetPreviewFromId(v))) - : of([])), - map(arr => arr.filter(v => determinePreviewFileType(v) === EnumPreviewFileTypes.VOLUMES)) - ) - - private diffPreviewingDatasetFiles$= this.previewingDatasetFiles$.pipe( - debounceTime(100), - startWith([] as IDatasetPreviewData[]), - pairwise(), - map(([ oldPreviewWidgets, newPreviewWidgets ]) => { - const oldPrvWgtIdSet = new Set(oldPreviewWidgets.map(DatasetPreviewGlue.GetDatasetPreviewId)) - const newPrvWgtIdSet = new Set(newPreviewWidgets.map(DatasetPreviewGlue.GetDatasetPreviewId)) - - const prvToShow = newPreviewWidgets.filter(obj => !oldPrvWgtIdSet.has(DatasetPreviewGlue.GetDatasetPreviewId(obj))) - const prvToDismiss = oldPreviewWidgets.filter(obj => !newPrvWgtIdSet.has(DatasetPreviewGlue.GetDatasetPreviewId(obj))) - - return { prvToShow, prvToDismiss } - }), - ) - - ngOnDestroy(){ - while(this.subscriptions.length > 0){ - this.subscriptions.pop().unsubscribe() - } - } - - private sharedDiffObs$ = this.diffPreviewingDatasetFiles$.pipe( - switchMap(({ prvToShow, prvToDismiss }) => { - return forkJoin({ - prvToShow: prvToShow.length > 0 - ? forkJoin(prvToShow.map(val => this.getDatasetPreviewFromId(val))) - : of([]), - prvToDismiss: prvToDismiss.length > 0 - ? forkJoin(prvToDismiss.map(val => this.getDatasetPreviewFromId(val))) - : of([]) - }) - }), - map(prvFilterNull), - shareReplay(1) - ) - - private getDiffDatasetFilesPreviews(filterFn: (prv: any) => boolean = () => true): Observable<{prvToShow: any[], prvToDismiss: any[]}>{ - return this.sharedDiffObs$.pipe( - map(({ prvToDismiss, prvToShow }) => { - return { - prvToShow: prvToShow.filter(filterFn), - prvToDismiss: prvToDismiss.filter(filterFn), - } - }) - ) - } - - private fetchedDatasetPreviewCache: Map<string, Observable<any>> = new Map() - public getDatasetPreviewFromId({ datasetSchema = 'minds/core/dataset/v1.0.0', datasetId, filename }: IDatasetPreviewData){ - const dsPrvId = DatasetPreviewGlue.GetDatasetPreviewId({ datasetSchema, datasetId, filename }) - const cachedPrv$ = this.fetchedDatasetPreviewCache.get(dsPrvId) - const filteredDsId = /[a-f0-9-]+$/.exec(datasetId) - if (cachedPrv$) return cachedPrv$ - const url = `${DS_PREVIEW_URL}/${encodeURIComponent(datasetSchema)}/${filteredDsId}/${encodeURIComponent(filename)}` - const filedetail$ = this.http.get(url, { responseType: 'json' }).pipe( - map(json => { - return { - ...json, - filename, - datasetId, - datasetSchema - } - }), - catchError((_err, _obs) => of(null)) - ) - this.fetchedDatasetPreviewCache.set(dsPrvId, filedetail$) - return filedetail$.pipe( - tap(val => this.fetchedDatasetPreviewCache.set(dsPrvId, of(val))) - ) - } - - constructor( - private store$: Store<any>, - private http: HttpClient, - ){ - - // managing dataset previews without UI - - // managing registeredVolumes - this.subscriptions.push( - this.getDiffDatasetFilesPreviews( - dsPrv => determinePreviewFileType(dsPrv) === EnumPreviewFileTypes.VOLUMES - ).pipe( - withLatestFrom(this.store$.pipe( - select(state => state?.viewerState?.templateSelected || null), - distinctUntilChanged(), - )) - ).subscribe(([ { prvToShow, prvToDismiss }, templateSelected ]) => { - const filterdPrvs = prvToShow.filter(prv => DatasetPreviewGlue.PreviewFileIsInCorrectSpace(prv, templateSelected)) - for (const prv of filterdPrvs) { - const { volumes } = prv['data']['iav-registered-volumes'] - this.store$.dispatch(ngViewerActionAddNgLayer({ - layer: volumes - })) - } - - for (const prv of prvToDismiss) { - const { volumes } = prv['data']['iav-registered-volumes'] - this.store$.dispatch(ngViewerActionRemoveNgLayer({ - layer: volumes - })) - } - }) - ) - } - - public datasetPreviewDisplayed(file: DatasetPreview, dataset?: IKgDataEntry){ - if (!file) return of(false) - return this.previewingDatasetFiles$.pipe( - map(datasetPreviews => { - const { filename, datasetId } = file - const { fullId } = dataset || {} - const { kgId } = getIdObj(fullId) || {} - - return datasetPreviews.findIndex(({ datasetId: dsId, filename: fName }) => { - return (datasetId || kgId) === dsId && fName === filename - }) >= 0 - }) - ) - } - - public displayDatasetPreview(previewFile: DatasetPreview, dataset: IKgDataEntry){ - const { filename, datasetId } = previewFile - const { fullId } = dataset - const { kgId, kgSchema } = getIdObj(fullId) - - const datasetPreviewFile = { - datasetSchema: kgSchema, - datasetId: datasetId || kgId, - filename - } - - this.store$.dispatch(glueActionToggleDatasetPreview({ datasetPreviewFile })) - } -} - -export function datasetPreviewMetaReducer(reducer: ActionReducer<any>): ActionReducer<any>{ - return function (state, action) { - switch(action.type) { - case glueActionToggleDatasetPreview.type: { - - const previewingDatasetFiles = (state?.uiState?.previewingDatasetFiles || []) as IDatasetPreviewData[] - const ids = new Set(previewingDatasetFiles.map(DatasetPreviewGlue.GetDatasetPreviewId)) - const { datasetPreviewFile } = action as Action & { datasetPreviewFile: IDatasetPreviewData } - const newId = DatasetPreviewGlue.GetDatasetPreviewId(datasetPreviewFile) - if (ids.has(newId)) { - const removeId = DatasetPreviewGlue.GetDatasetPreviewId(datasetPreviewFile) - const filteredOpenedWidgets = previewingDatasetFiles.filter(obj => { - const id = DatasetPreviewGlue.GetDatasetPreviewId(obj) - return id !== removeId - }) - return reducer(state, uiActionSetPreviewingDatasetFiles({ previewingDatasetFiles: filteredOpenedWidgets })) - } else { - return reducer(state, uiActionSetPreviewingDatasetFiles({ previewingDatasetFiles: [ ...previewingDatasetFiles, datasetPreviewFile ] })) - } - } - case glueActionAddDatasetPreview.type: { - const previewingDatasetFiles = (state?.uiState?.previewingDatasetFiles || []) as IDatasetPreviewData[] - const { datasetPreviewFile } = action as Action & { datasetPreviewFile: IDatasetPreviewData } - return reducer(state, uiActionSetPreviewingDatasetFiles({ previewingDatasetFiles: [ ...previewingDatasetFiles, datasetPreviewFile] })) - } - case glueActionRemoveDatasetPreview.type: { - const previewingDatasetFiles = (state?.uiState?.previewingDatasetFiles || []) as IDatasetPreviewData[] - const { datasetPreviewFile } = action as any - - const removeId = DatasetPreviewGlue.GetDatasetPreviewId(datasetPreviewFile) - const filteredOpenedWidgets = previewingDatasetFiles.filter(obj => { - const id = DatasetPreviewGlue.GetDatasetPreviewId(obj) - return id !== removeId - }) - return reducer(state, uiActionSetPreviewingDatasetFiles({ previewingDatasetFiles: filteredOpenedWidgets })) - } - default: return reducer(state, action) - } - } -} - -export const SAVE_USER_DATA = new InjectionToken<TypeSaveUserData>('SAVE_USER_DATA') - -type TypeSaveUserData = (key: string, value: string) => void - -export const gluActionFavDataset = createAction( - '[glue] favDataset', - props<{dataentry: Partial<IKgDataEntry>}>() -) -export const gluActionUnfavDataset = createAction( - '[glue] favDataset', - props<{dataentry: Partial<IKgDataEntry>}>() -) -export const gluActionToggleDataset = createAction( - '[glue] favDataset', - props<{dataentry: Partial<IKgDataEntry>}>() -) -export const gluActionSetFavDataset = createAction( - '[glue] favDataset', - props<{dataentries: Partial<IKgDataEntry>[]}>() -) @Injectable({ providedIn: 'root' @@ -467,28 +26,3 @@ export class ClickInterceptorService extends RegDeregController<any, boolean>{ // called when the call has not been intercepted } } - -export type _TPLIVal = { - name: string - filename: string - datasetSchema: string - datasetId: string - data: { - 'iav-registered-volumes': { - volumes: { - name: string - source: string - shader: string - transform: any - opacity: string - }[] - } - } - referenceSpaces: { - name: string - fullId: string - }[] - mimetype: 'application/json' -} - -export const _PLI_VOLUME_INJ_TOKEN = new InjectionToken<Observable<_TPLIVal[]>>('_PLI_VOLUME_INJ_TOKEN') diff --git a/src/index.html b/src/index.html index d1c2ffdeaeb72510b344f3053835e93ac1a2e1e9..56e924d2b7181e2344f5bb912174a8013a52bde2 100644 --- a/src/index.html +++ b/src/index.html @@ -13,10 +13,10 @@ <link rel="icon" type="image/png" href="assets/favicons/favicon-128-light.png"/> <script src="extra_js.js"></script> <script src="https://unpkg.com/kg-dataset-previewer@1.2.0/dist/kg-dataset-previewer/kg-dataset-previewer.js" defer></script> - <script src="https://unpkg.com/three-surfer@0.0.10/dist/bundle.js" defer></script> + <script src="https://unpkg.com/three-surfer@0.0.11/dist/bundle.js" defer></script> <script type="module" src="https://unpkg.com/ng-layer-tune@0.0.6/dist/ng-layer-tune/ng-layer-tune.esm.js"></script> - - <title>Interactive Atlas Viewer</title> + <script type="module" src="https://unpkg.com/hbp-connectivity-component@0.6.2/dist/connectivity-component/connectivity-component.js" ></script> + <title>Siibra Explorer</title> </head> <body> <atlas-viewer> diff --git a/src/keyframesModule/service.ts b/src/keyframesModule/service.ts index b807aa15ba8414c823bf48ab132fb01c29c19590..98575c6b3fc1f6de5439791eaf01d0609c580d92 100644 --- a/src/keyframesModule/service.ts +++ b/src/keyframesModule/service.ts @@ -1,11 +1,8 @@ import { Injectable, OnDestroy } from "@angular/core"; -import { MatDialog } from "@angular/material/dialog"; import { Store } from "@ngrx/store"; import { BehaviorSubject, Subscription } from "rxjs"; import { distinctUntilChanged } from "rxjs/operators"; -import { viewerStateSetViewerMode } from "src/services/state/viewerState.store.helper"; -import { KEYFRAME_VIEWMODE } from "./constants"; -import { KeyFrameCtrlCmp } from "./keyframeCtrl/keyframeCtrl.component"; +import { actions } from "src/state/atlasSelection"; @Injectable() export class KeyFrameService implements OnDestroy { @@ -42,8 +39,10 @@ export class KeyFrameService implements OnDestroy { // TODO enable side bar when ready this.store.dispatch( - viewerStateSetViewerMode({ - payload: flag && KEYFRAME_VIEWMODE + actions.setViewerMode({ + viewerMode: flag + ? "key frame" + : null }) ) }) diff --git a/src/layouts/currentLayout/currentLayout.component.ts b/src/layouts/currentLayout/currentLayout.component.ts index be31bf00347ec1bee75eba254c3ff866b7ac4304..0e6a4b56db66d3afbd888e7bb66a9bace4a0a8a5 100644 --- a/src/layouts/currentLayout/currentLayout.component.ts +++ b/src/layouts/currentLayout/currentLayout.component.ts @@ -1,9 +1,5 @@ -import { Component } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { Observable } from "rxjs"; -import { startWith } from "rxjs/operators"; -import { SUPPORTED_PANEL_MODES } from "src/services/state/ngViewerState.store"; -import { ngViewerSelectorPanelMode } from "src/services/state/ngViewerState/selectors"; +import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; +import { userInterface } from "src/state" @Component({ selector: 'current-layout', @@ -11,19 +7,20 @@ import { ngViewerSelectorPanelMode } from "src/services/state/ngViewerState/sele styleUrls: [ './currentLayout.style.css', ], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class CurrentLayout { - public supportedPanelModes = SUPPORTED_PANEL_MODES - public panelMode$: Observable<string> - - constructor( - private store$: Store<any>, - ) { - this.panelMode$ = this.store$.pipe( - select(ngViewerSelectorPanelMode), - startWith(SUPPORTED_PANEL_MODES[0]), - ) + @Input('current-layout-use-layout') + public useLayout: userInterface.PanelMode = "FOUR_PANEL" + + public panelModes: Record<string, userInterface.PanelMode> = { + FOUR_PANEL: "FOUR_PANEL", + H_ONE_THREE: "H_ONE_THREE", + SINGLE_PANEL: "SINGLE_PANEL", + V_ONE_THREE: "V_ONE_THREE" } + + public panelMode: userInterface.PanelMode = null } diff --git a/src/layouts/currentLayout/currentLayout.template.html b/src/layouts/currentLayout/currentLayout.template.html index 9575932ed1de52ea23341d6570a48b32225c9160..1079302ee3c7ae96177427ef21ad262d96f1a2ad 100644 --- a/src/layouts/currentLayout/currentLayout.template.html +++ b/src/layouts/currentLayout/currentLayout.template.html @@ -1,7 +1,7 @@ -<ng-container [ngSwitch]="panelMode$ | async"> +<ng-container [ngSwitch]="useLayout"> <layout-four-panel - *ngSwitchCase="supportedPanelModes[0]" - class="d-block w-100 h-100"> + *ngSwitchCase="panelModes.FOUR_PANEL" + class="d-block sxplr-w-100 sxplr-h-100"> <ng-container cell-i> <ng-content *ngTemplateOutlet="celli"></ng-content> </ng-container> @@ -16,8 +16,8 @@ </ng-container> </layout-four-panel> <layout-horizontal-one-three - *ngSwitchCase="supportedPanelModes[1]" - class="d-block w-100 h-100"> + *ngSwitchCase="panelModes.H_ONE_THREE" + class="d-block sxplr-w-100 sxplr-h-100"> <ng-container cell-i> <ng-content *ngTemplateOutlet="celli"></ng-content> </ng-container> @@ -32,8 +32,8 @@ </ng-container> </layout-horizontal-one-three> <layout-vertical-one-three - *ngSwitchCase="supportedPanelModes[2]" - class="d-block w-100 h-100"> + *ngSwitchCase="panelModes.V_ONE_THREE" + class="d-block sxplr-w-100 sxplr-h-100"> <ng-container cell-i> <ng-content *ngTemplateOutlet="celli"></ng-content> </ng-container> @@ -48,8 +48,8 @@ </ng-container> </layout-vertical-one-three> <layout-single-panel - *ngSwitchCase="supportedPanelModes[3]" - class="d-block w-100 h-100"> + *ngSwitchCase="panelModes.SINGLE_PANEL" + class="d-block sxplr-w-100 sxplr-h-100"> <ng-container cell-i> <ng-content *ngTemplateOutlet="celli"></ng-content> </ng-container> @@ -64,7 +64,7 @@ </ng-container> </layout-single-panel> <div *ngSwitchDefault> - A panel mode which I have never seen before ... + A panel mode which I have never seen before ... {{ useLayout }} </div> </ng-container> diff --git a/src/layouts/floating/floating.component.ts b/src/layouts/floating/floating.component.ts deleted file mode 100644 index bb8ec5176576e4d6c0b2c06db51aea133237a5b9..0000000000000000000000000000000000000000 --- a/src/layouts/floating/floating.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, HostBinding, Input } from "@angular/core"; - -@Component({ - selector : 'layout-floating-container', - templateUrl : './floating.template.html', - styleUrls : [ - `./floating.style.css`, - ], -}) - -export class FloatingLayoutContainer { - @HostBinding('style.z-index') - @Input() - public zIndex: number = 5 -} diff --git a/src/layouts/floating/floating.style.css b/src/layouts/floating/floating.style.css deleted file mode 100644 index 0cb15ee43e183a4d6bb193608840da0b4886934f..0000000000000000000000000000000000000000 --- a/src/layouts/floating/floating.style.css +++ /dev/null @@ -1,15 +0,0 @@ -:host -{ - position: absolute; - top:0; - left:0; - width:100%; - height:100%; - display:block; - pointer-events: none; -} - -:host * -{ - pointer-events: all; -} \ No newline at end of file diff --git a/src/layouts/floating/floating.template.html b/src/layouts/floating/floating.template.html deleted file mode 100644 index d7b4509bdd7e7b9ac0c84063fdc18c606fb96a9f..0000000000000000000000000000000000000000 --- a/src/layouts/floating/floating.template.html +++ /dev/null @@ -1,2 +0,0 @@ -<ng-content> -</ng-content> \ No newline at end of file diff --git a/src/layouts/fourCorners/fourCorners.component.ts b/src/layouts/fourCorners/fourCorners.component.ts index 6bae516c960e2c6b3e7344b8bb46274a310d1c95..4591f7d869e27d448e144876e498b7626d7716fa 100644 --- a/src/layouts/fourCorners/fourCorners.component.ts +++ b/src/layouts/fourCorners/fourCorners.component.ts @@ -1,11 +1,12 @@ -import { Component, Input } from "@angular/core"; +import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; @Component({ selector: 'iav-layout-fourcorners', templateUrl: './fourCorners.template.html', styleUrls: [ './fourCorners.style.css' - ] + ], + changeDetection: ChangeDetectionStrategy.OnPush }) export class FourCornersCmp{ diff --git a/src/layouts/fourCorners/fourCorners.template.html b/src/layouts/fourCorners/fourCorners.template.html index c0e98df279d32db83b96227fa439642ff79175cc..6f837d73ffd82b84b4f6bef6f592f38b94ea7012 100644 --- a/src/layouts/fourCorners/fourCorners.template.html +++ b/src/layouts/fourCorners/fourCorners.template.html @@ -1,4 +1,4 @@ -<div class="position-absolute top-0 left-0 w-100 h-100"> +<div class="position-absolute tosxplr-p-0 left-0 w-100 h-100"> <ng-content select="[iavLayoutFourCornersContent]"></ng-content> </div> diff --git a/src/layouts/layout.module.ts b/src/layouts/layout.module.ts index 0923ccb50102bfcde21c45a8f8bafe250ba31863..40cedc4f12736cadd766c749d55ab3e1139b4a43 100644 --- a/src/layouts/layout.module.ts +++ b/src/layouts/layout.module.ts @@ -3,7 +3,6 @@ import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { ComponentsModule } from "../components/components.module"; import { CurrentLayout } from "./currentLayout/currentLayout.component"; -import { FloatingLayoutContainer } from "./floating/floating.component"; import { FourCornersCmp } from "./fourCorners/fourCorners.component"; import { FourPanelLayout } from "./layouts/fourPanel/fourPanel.component"; import { HorizontalOneThree } from "./layouts/h13/h13.component"; @@ -17,7 +16,6 @@ import { VerticalOneThree } from "./layouts/v13/v13.component"; ComponentsModule, ], declarations : [ - FloatingLayoutContainer, FourCornersCmp, CurrentLayout, @@ -28,7 +26,6 @@ import { VerticalOneThree } from "./layouts/v13/v13.component"; ], exports : [ BrowserAnimationsModule, - FloatingLayoutContainer, FourCornersCmp, CurrentLayout, FourPanelLayout, diff --git a/src/main-common.ts b/src/main-common.ts index 3bfc08878e14626e0a9cc0c95d26806ed27a47e3..f927f25516b9280f7f21416551c27a338117177e 100644 --- a/src/main-common.ts +++ b/src/main-common.ts @@ -28,7 +28,6 @@ import '!!file-loader?name=version.css!src/version.css' import 'zone.js' import { enableProdMode } from '@angular/core'; -import * as ConnectivityComponent from 'hbp-connectivity-component/dist/loader' import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' import { MainModule } from './main.module'; @@ -39,5 +38,3 @@ if (PRODUCTION) { console.log(`Siibra Explorer: ${VERSION}::${GIT_HASH}`) } platformBrowserDynamic().bootstrapModule(MainModule) - -ConnectivityComponent.defineCustomElements(window) \ No newline at end of file diff --git a/src/main.module.ts b/src/main.module.ts index 93dd5e3db578cc3a369755cbe82c430fca6f32ea..7d26a8ee52400fa2acd8e9cbd0a670ddd06c5670 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -2,84 +2,66 @@ import { DragDropModule } from '@angular/cdk/drag-drop' import { CommonModule } from "@angular/common"; import { APP_INITIALIZER, NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; -import { StoreModule, ActionReducer, Store } from "@ngrx/store"; +import { Store, select } from "@ngrx/store"; import { AngularMaterialModule } from 'src/sharedModules' import { AtlasViewer } from "./atlasViewer/atlasViewer.component"; import { ComponentsModule } from "./components/components.module"; import { LayoutModule } from "./layouts/layout.module"; -import { ngViewerState, pluginState, uiState, userConfigState, UserConfigStateUseEffect, viewerConfigState, viewerState } from "./services/stateStore.service"; import { UIModule } from "./ui/ui.module"; import { HttpClientModule } from "@angular/common/http"; -import { EffectsModule } from "@ngrx/effects"; import { AtlasWorkerService } from "./atlasViewer/atlasViewer.workerService.service"; import { WINDOW_MESSAGING_HANDLER_TOKEN } from 'src/messaging/types' -import { ConfirmDialogComponent } from "./components/confirmDialog/confirmDialog.component"; -import { DialogComponent } from "./components/dialog/dialog.component"; import { DialogService } from "./services/dialogService.service"; -import { UseEffects } from "./services/effect/effect"; -import { LocalFileService } from "./services/localFile.service"; -import { NgViewerUseEffect } from "./services/state/ngViewerState.store"; -import { ViewerStateUseEffect } from "./services/state/viewerState.store"; import { UIService } from "./services/uiService.service"; -import { ViewerStateControllerUseEffect } from "src/state"; -import { FloatingContainerDirective } from "./util/directives/floatingContainer.directive"; -import { FloatingMouseContextualContainerDirective } from "./util/directives/floatingMouseContextualContainer.directive"; -import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR, PureContantService, UtilModule } from "src/util"; +import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR, UtilModule } from "src/util"; import { SpotLightModule } from 'src/spotlight/spot-light.module' import { TryMeComponent } from "./ui/tryme/tryme.component"; -import { UiStateUseEffect } from "src/services/state/uiState.store"; -import { PluginServiceUseEffect } from './services/effect/pluginUseEffect'; -import { TemplateCoordinatesTransformation } from "src/services/templateCoordinatesTransformation.service"; -import { NewTemplateUseEffect } from './services/effect/newTemplate.effect'; +import { InterSpaceCoordXformSvc } from "src/atlasComponents/sapi/core/space/interSpaceCoordXform.service"; import { WidgetModule } from 'src/widget'; import { PluginModule } from './plugin/plugin.module'; import { LoggingModule } from './logging/logging.module'; import { AuthService } from './auth' import 'src/theme.scss' -import { DatasetPreviewGlue, datasetPreviewMetaReducer, IDatasetPreviewGlue, GlueEffects, ClickInterceptorService, _PLI_VOLUME_INJ_TOKEN } from './glue'; -import { viewerStateHelperReducer, viewerStateMetaReducers, ViewerStateHelperEffect } from './services/state/viewerState.store.helper'; +import { ClickInterceptorService } from './glue'; import { TOS_OBS_INJECTION_TOKEN } from './ui/kgtos'; -import { UiEffects } from './services/state/uiState/ui.effects'; import { MesssagingModule } from './messaging/module'; -import { ParcellationRegionModule } from './atlasComponents/parcellationRegion'; -import { ViewerModule, VIEWERMODULE_DARKTHEME } from './viewerModule'; +import { ViewerModule } from './viewerModule'; import { CookieModule } from './ui/cookieAgreement/module'; import { KgTosModule } from './ui/kgtos/module'; -import { MouseoverModule } from './mouseoverModule/mouseover.module'; import { AtlasViewerRouterModule } from './routerModule'; import { MessagingGlue } from './messagingGlue'; import { BS_ENDPOINT } from './util/constants'; import { QuickTourModule } from './ui/quickTour'; import { of } from 'rxjs'; -import { debounceTime, filter, map } from 'rxjs/operators'; -import { GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME, OVERRIDE_IAV_DATASET_PREVIEW_DATASET_FN, kgTos, IAV_DATASET_PREVIEW_ACTIVE } from './databrowser.fallback' -import { CANCELLABLE_DIALOG } from './util/interfaces'; +import { CANCELLABLE_DIALOG, CANCELLABLE_DIALOG_OPTS } from './util/interfaces'; import { environment } from 'src/environments/environment' import { NotSupportedCmp } from './notSupportedCmp/notSupported.component'; -import { RouterService } from './routerModule/router.service'; -import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer } from './services/state/ngViewerState.store.helper'; +import { + atlasSelection, + RootStoreModule, + getStoreEffects, +} from "./state" +import { DARKTHEME } from './util/injectionTokens'; +import { map } from 'rxjs/operators'; +import { EffectsModule } from '@ngrx/effects'; -export function debug(reducer: ActionReducer<any>): ActionReducer<any> { - return function(state, action) { - console.log('state', state); - console.log('action', action); - - return reducer(state, action); - }; -} +// TODO check if there is a more logical place import put layerctrl effects ts +import { LayerCtrlEffects } from './viewerModule/nehuba/layerCtrl.service/layerCtrl.effects'; +import { NehubaNavigationEffects } from './viewerModule/nehuba/navigation.service/navigation.effects'; +import { CONST } from "common/constants" @NgModule({ - imports : [ + imports: [ FormsModule, CommonModule, LayoutModule, ComponentsModule, DragDropModule, UIModule, - + AngularMaterialModule, UtilModule, WidgetModule, @@ -88,74 +70,36 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { MesssagingModule, ViewerModule, SpotLightModule, - ParcellationRegionModule, CookieModule, KgTosModule, - MouseoverModule, AtlasViewerRouterModule, QuickTourModule, EffectsModule.forRoot([ - UseEffects, - UserConfigStateUseEffect, - ViewerStateControllerUseEffect, - ViewerStateUseEffect, - NgViewerUseEffect, - PluginServiceUseEffect, - UiStateUseEffect, - NewTemplateUseEffect, - ViewerStateHelperEffect, - GlueEffects, - UiEffects, + ...getStoreEffects(), + LayerCtrlEffects, + NehubaNavigationEffects, ]), - StoreModule.forRoot({ - pluginState, - viewerConfigState, - ngViewerState, - viewerState, - viewerStateHelper: viewerStateHelperReducer, - uiState, - userConfigState, - },{ - metaReducers: [ - // debug, - ...viewerStateMetaReducers, - datasetPreviewMetaReducer, - ] - }), + RootStoreModule, HttpClientModule, ], - declarations : [ + declarations: [ AtlasViewer, NotSupportedCmp, TryMeComponent, - /* directives */ - FloatingContainerDirective, - FloatingMouseContextualContainerDirective, - - ], - entryComponents : [ - DialogComponent, - ConfirmDialogComponent, ], - providers : [ + providers: [ AtlasWorkerService, AuthService, - LocalFileService, DialogService, UIService, - TemplateCoordinatesTransformation, + InterSpaceCoordXformSvc, ClickInterceptorService, - { - provide: OVERRIDE_IAV_DATASET_PREVIEW_DATASET_FN, - useFactory: (glue: IDatasetPreviewGlue) => glue.displayDatasetPreview.bind(glue), - deps: [ DatasetPreviewGlue ] - }, { provide: CANCELLABLE_DIALOG, useFactory: (uiService: UIService) => { - return (message, option) => { + return (message: string, option: CANCELLABLE_DIALOG_OPTS) => { const actionBtn = { type: 'mat-stroked-button', color: 'default', @@ -190,26 +134,9 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { }, deps: [ UIService ] }, - { - provide: _PLI_VOLUME_INJ_TOKEN, - useFactory: (glue: DatasetPreviewGlue) => glue._volumePreview$, - deps: [ DatasetPreviewGlue ] - }, - { - provide: IAV_DATASET_PREVIEW_ACTIVE, - useFactory: (glue: DatasetPreviewGlue) => glue.datasetPreviewDisplayed.bind(glue), - deps: [ DatasetPreviewGlue ] - }, - { - provide: GET_KGDS_PREVIEW_INFO_FROM_ID_FILENAME, - useFactory: (glue: DatasetPreviewGlue) => glue.getDatasetPreviewFromId.bind(glue), - deps: [ DatasetPreviewGlue ] - }, - DatasetPreviewGlue, - { provide: TOS_OBS_INJECTION_TOKEN, - useValue: of(kgTos) + useValue: of(CONST.KG_TOS) }, { @@ -232,11 +159,6 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { ClickInterceptorService ] }, - { - provide: VIEWERMODULE_DARKTHEME, - useFactory: (pureConstantService: PureContantService) => pureConstantService.darktheme$, - deps: [ PureContantService ] - }, { provide: WINDOW_MESSAGING_HANDLER_TOKEN, useClass: MessagingGlue @@ -246,66 +168,26 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { useValue: (environment.BS_REST_URL || `https://siibra-api-stable.apps.hbp.eu/v1_0`).replace(/\/$/, '') }, { - /** - * monkey patch 1um data as x-voi:d71d369a-c401-4d7e-b97a-3fb78eed06c5 - */ + provide: DARKTHEME, + useFactory: (store: Store) => store.pipe( + select(atlasSelection.selectors.selectedTemplate), + map(tmpl => !!(tmpl && tmpl["@id"] !== 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588')), + ), + deps: [ Store ] + }, + { provide: APP_INITIALIZER, - useFactory: (rSvc: RouterService, store: Store) => { - rSvc.customRoute$.pipe( - map(val => val[`x-voi`] === `d71d369a-c401-4d7e-b97a-3fb78eed06c5`), - debounceTime(160) - ).subscribe(flag => { - if (flag) { - store.dispatch( - ngViewerActionAddNgLayer({ - layer: [{ - name: `VOI_1 (area V1)`, - source: `precomputed://https://1um.brainatlas.eu/cyto_reconstructions/ebrains_release/BB_1um/VOI_1/precomputed`, - shader: `void main(){ emitGrayscale(toNormalized(getDataValue()));}`, - transform: [[1.002276062965393,0.1810370832681656,0.15283183753490448,-10704839],[-0.14879435300827026,-0.0360119566321373,1.018455982208252,-60994436],[0.18436983227729797,-1.0132216215133667,-0.008890812285244465,-2825862.75],[0,0,0,1]], - visible: true, - opacity: 1, - },{ - name: `VOI_2 (area V2)`, - source: `precomputed://https://1um.brainatlas.eu/cyto_reconstructions/ebrains_release/BB_1um/VOI_2/precomputed`, - shader: `void main(){ emitGrayscale(toNormalized(getDataValue()));}`, - // transform: [[1.002276062965393,0.1810370832681656,0.15283183753490448,-10704839],[-0.14879435300827026,-0.0360119566321373,1.018455982208252,-60994436],[0.18436983227729797,-1.0132216215133667,-0.008890812285244465,-2825862.75],[0,0,0,1]], - visible: true, - opacity: 1, - }] - }) - ) - } else { - store.dispatch( - ngViewerActionRemoveNgLayer({ - layer: [{ - name: `VOI_1 (area V1)` - }, { - name: `VOI_2 (area V2)` - }] - }) - ) - } - }) + useFactory: (authSvc: AuthService) => { + authSvc.authReloadState() return () => Promise.resolve() }, multi: true, - deps: [ RouterService, Store ] + deps: [ AuthService ] } ], - bootstrap : [ + bootstrap: [ AtlasViewer, - ], + ] }) -export class MainModule { - - constructor( - authServce: AuthService, - // bandaid fix: required to init glueService on startup - // TODO figure out why, then init service without this hack - glueService: DatasetPreviewGlue - ) { - authServce.authReloadState() - } -} +export class MainModule {} diff --git a/src/messaging/nmvSwc/index.ts b/src/messaging/nmvSwc/index.ts index 08b0ce6994d4ad595d3aa1689e872e8d45a702b8..2f4e7586970acd9450a6456fb9fe6e06ba7ddea9 100644 --- a/src/messaging/nmvSwc/index.ts +++ b/src/messaging/nmvSwc/index.ts @@ -1,5 +1,5 @@ import { Observable, Subject } from "rxjs" -import { getUuid } from "src/util/fn" +import { getExportNehuba, getUuid } from "src/util/fn" import { IMessagingActions, IMessagingActionTmpl, TVec4, TMat4 } from "../types" import { INmvTransform } from "./type" @@ -110,7 +110,7 @@ export const processJsonLd = (json: { [key: string]: any }): Observable<IMessagi } }) - await waitFor(() => !!(window as any).export_nehuba) + await waitFor(() => !!getExportNehuba()) const b64Encoded = encoding.indexOf('base64') >= 0 const isGzipped = encoding.indexOf('gzip') >= 0 @@ -119,7 +119,7 @@ export const processJsonLd = (json: { [key: string]: any }): Observable<IMessagi data = atob(data) } if (isGzipped) { - data = (window as any).export_nehuba.pako.inflate(data) + data = getExportNehuba().pako.inflate(data) } let output = `` for (let i = 0; i < data.length; i++) { @@ -146,7 +146,7 @@ export const processJsonLd = (json: { [key: string]: any }): Observable<IMessagi ] // NG translation works on nm scale const scaleUmToNm = 1e3 - const { mat3, vec3 } = (window as any).export_nehuba + const { mat3, vec3 } = getExportNehuba() const modA = mat3.fromValues( scaleUmToVoxelFixed[0], 0, 0, 0, scaleUmToVoxelFixed[1], 0, diff --git a/src/messaging/service.spec.ts b/src/messaging/service.spec.ts index 5ee3b0811946b615c8e0348cd1018144a8220e83..2ba18423e3617ed2c3016bc9467083fc49576ff9 100644 --- a/src/messaging/service.spec.ts +++ b/src/messaging/service.spec.ts @@ -1,7 +1,6 @@ import { TestBed } from "@angular/core/testing" import { MockStore, provideMockStore } from "@ngrx/store/testing" import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service" -import { viewerStateFetchedAtlasesSelector } from "src/services/state/viewerState/selectors" import { AngularMaterialModule } from "src/sharedModules" import { getUuid } from "src/util/fn" import { IAV_POSTMESSAGE_NAMESPACE, MessagingService } from "./service" @@ -39,8 +38,6 @@ describe('> service.ts', () => { ] }) - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, []) }) it('> can be inst', () => { diff --git a/src/messaging/service.ts b/src/messaging/service.ts index 7739108fca2d3bab0cf4e3c6a6aed12e148a931a..d810a7fce7c330809be7154671389d090ef18bca 100644 --- a/src/messaging/service.ts +++ b/src/messaging/service.ts @@ -7,7 +7,7 @@ import { getUuid } from "src/util/fn"; import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; import { ConfirmDialogComponent } from "src/components/confirmDialog/confirmDialog.component"; -import { IMessagingActions, IMessagingActionTmpl, ILoadMesh, LOAD_MESH_TOKEN, WINDOW_MESSAGING_HANDLER_TOKEN, IWindowMessaging } from './types' +import { IMessagingActions, IMessagingActionTmpl, WINDOW_MESSAGING_HANDLER_TOKEN, IWindowMessaging } from './types' import { TYPE as NMV_TYPE, processJsonLd as nmvProcess } from './nmvSwc/index' import { TYPE as NATIVE_TYPE, processJsonLd as nativeProcess } from './native' @@ -35,7 +35,6 @@ export class MessagingService { private snackbar: MatSnackBar, private worker: AtlasWorkerService, @Optional() @Inject(WINDOW_MESSAGING_HANDLER_TOKEN) private messagingHandler: IWindowMessaging, - @Optional() @Inject(LOAD_MESH_TOKEN) private loadMesh: (loadMeshParam: ILoadMesh) => void, ){ if (window.opener){ @@ -72,11 +71,13 @@ export class MessagingService { result }, origin) } catch (error) { - src.postMessage({ - id, - jsonrpc: '2.0', - error - }, origin) + if (src) { + src.postMessage({ + id, + jsonrpc: '2.0', + error + }, origin) + } } }) @@ -189,17 +190,21 @@ export class MessagingService { }) isLoadingSnack?.dismiss() const meshId = 'bobby' - if (this.loadMesh) { - const { objectUrl, customFragmentColor } = resp.result || {} - this.loadMesh({ - type: 'VTK', - id: meshId, - url: objectUrl, - customFragmentColor - }) - } else { - this.snackbar.open(`Error: loadMesh method not injected.`) - } + /** + * TODO re-enable plotly VTK mesh + */ + + // if (false) { + // const { objectUrl, customFragmentColor } = resp.result || {} + // this.loadMesh({ + // type: 'VTK', + // id: meshId, + // url: objectUrl, + // customFragmentColor + // }) + // } else { + // this.snackbar.open(`Error: loadMesh method not injected.`) + // } return 'OK' } diff --git a/src/messaging/types.ts b/src/messaging/types.ts index eda7bc06a35931dd508ead545b9bb7f6df38c174..6eb83f851f40a7da56d1e8aad529d573a32f31a9 100644 --- a/src/messaging/types.ts +++ b/src/messaging/types.ts @@ -8,10 +8,10 @@ interface IResourceType { swc: string } -export type TVec4 = [number, number, number, number] -export type TVec3 = [number, number, number] -export type TMat3 = [TVec3, TVec3, TVec3] -export type TMat4 = [TVec4, TVec4, TVec4, TVec4] +export type TVec4 = number[] +export type TVec3 = number[] +export type TMat3 = TVec3[] +export type TMat4 = TVec4[] interface ICommonResParam { transform: TMat4 @@ -45,14 +45,6 @@ export interface IMessagingActions<TAction extends keyof IMessagingActionTmpl> { payload: IMessagingActionTmpl[TAction] } -export interface ILoadMesh { - type: 'VTK' - id: string - url: string - customFragmentColor?: string -} -export const LOAD_MESH_TOKEN = new InjectionToken<(loadMeshParam: ILoadMesh) => void>('LOAD_MESH_TOKEN') - export interface IWindowMessaging { loadTempladById(payload: IMessagingActionTmpl['loadTemplate']): void loadResource(payload: IMessagingActionTmpl['loadResource']): void diff --git a/src/messagingGlue.ts b/src/messagingGlue.ts index 80e459157a717ec90b21f426596e531acbb48843..724841a7d13e9255e76ba5c4b0ad94e7393e91ad 100644 --- a/src/messagingGlue.ts +++ b/src/messagingGlue.ts @@ -1,10 +1,8 @@ import { Injectable, OnDestroy } from "@angular/core"; -import { select, Store } from "@ngrx/store"; +import { Store } from "@ngrx/store"; import { IMessagingActionTmpl, IWindowMessaging } from "./messaging/types"; -import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer } from "./services/state/ngViewerState/actions"; -import { viewerStateSelectAtlas } from "./services/state/viewerState/actions"; -import { viewerStateFetchedAtlasesSelector } from "./services/state/viewerState/selectors"; -import { generalActionError } from "./services/stateStore.helper"; +import { atlasAppearance, atlasSelection, generalActions } from "src/state" +import { SAPI } from "./atlasComponents/sapi"; @Injectable() export class MessagingGlue implements IWindowMessaging, OnDestroy { @@ -17,14 +15,15 @@ export class MessagingGlue implements IWindowMessaging, OnDestroy { while(this.onDestroyCb.length > 0) this.onDestroyCb.pop()() } - constructor(private store: Store<any>){ + constructor( + private store: Store<any>, + sapi: SAPI, + ){ - const sub = this.store.pipe( - select(viewerStateFetchedAtlasesSelector) - ).subscribe((atlases: any[]) => { + const sub = sapi.atlases$.subscribe(atlases => { for (const atlas of atlases) { - const { ['@id']: atlasId, templateSpaces } = atlas - for (const tmpl of templateSpaces) { + const { ['@id']: atlasId, spaces } = atlas + for (const tmpl of spaces) { const { ['@id']: tmplId } = tmpl this.tmplSpIdToAtlasId.set(tmplId, atlasId) } @@ -42,19 +41,15 @@ export class MessagingGlue implements IWindowMessaging, OnDestroy { const atlasId = this.tmplSpIdToAtlasId.get(payload['@id']) if (!atlasId) { return this.store.dispatch( - generalActionError({ + generalActions.generalActionError({ message: `atlas id with the corresponding templateId ${payload['@id']} not found.` }) ) } this.store.dispatch( - viewerStateSelectAtlas({ - atlas: { - ['@id']: atlasId, - template: { - ['@id']: payload['@id'] - } - } + atlasSelection.actions.selectATPById({ + atlasId, + templateId: payload["@id"] }) ) } @@ -71,29 +66,25 @@ export class MessagingGlue implements IWindowMessaging, OnDestroy { if (type === 'swc') { const { transform } = resourceParam const layer = { - name: swcLayerUuid, id: swcLayerUuid, source: `swc://${url}`, - mixability: 'mixable', - type: "segmentation", - "segments": [ + segments: [ "1" ], - transform, + transform: transform, + clType: 'customlayer/nglayer' as const } this.store.dispatch( - ngViewerActionAddNgLayer({ - layer + atlasAppearance.actions.addCustomLayer({ + customLayer: layer }) ) this.mapIdUnload.set(swcLayerUuid, () => { this.store.dispatch( - ngViewerActionRemoveNgLayer({ - layer: { - name: swcLayerUuid - } + atlasAppearance.actions.removeCustomLayer({ + id: swcLayerUuid }) ) unload() diff --git a/src/mouseoverModule/mouseOverCvt.pipe.ts b/src/mouseoverModule/mouseOverCvt.pipe.ts index b0a86234237ad87ab895e70575e8dd1964ba5d51..f325feacc32b30c67e39451bac74cff846782185 100644 --- a/src/mouseoverModule/mouseOverCvt.pipe.ts +++ b/src/mouseoverModule/mouseOverCvt.pipe.ts @@ -4,16 +4,14 @@ import { TOnHoverObj } from "./util"; function render<T extends keyof TOnHoverObj>(key: T, value: TOnHoverObj[T]){ if (!value) return [] switch (key) { - case 'segments': { - return (value as TOnHoverObj['segments']).map(seg => { + case 'regions': { + return (value as TOnHoverObj['regions']).map(seg => { return { icon: { fontSet: 'fas', fontIcon: 'fa-brain' }, - text: typeof seg.segment === 'string' - ? seg.segment - : seg.segment.name + text: seg.name } }) } diff --git a/src/mouseoverModule/mouseover.directive.ts b/src/mouseoverModule/mouseover.directive.ts index cc4ae00872d32f4463aeb4d6a9ce7fbdee37ebe3..9e005530107e7653a559fe108c1b4891f9bc7847 100644 --- a/src/mouseoverModule/mouseover.directive.ts +++ b/src/mouseoverModule/mouseover.directive.ts @@ -1,11 +1,11 @@ import { Directive } from "@angular/core" import { select, Store } from "@ngrx/store" -import { merge, Observable } from "rxjs" +import { merge, NEVER, Observable, of } from "rxjs" import { distinctUntilChanged, map, scan, shareReplay } from "rxjs/operators" import { LoggingService } from "src/logging" -import { uiStateMouseOverLandmarkSelector, uiStateMouseOverSegmentsSelector, uiStateMouseoverUserLandmark } from "src/services/state/uiState/selectors" import { TOnHoverObj, temporalPositveScanFn } from "./util" import { ModularUserAnnotationToolService } from "src/atlasComponents/userAnnotations/tools/service"; +import { userInteraction } from "src/state" @Directive({ selector: '[iav-mouse-hover]', @@ -25,27 +25,29 @@ export class MouseHoverDirective { // TODO consider moving these into a single obs serviced by a DI service // can potentially net better performance - const onHoverUserLandmark$ = this.store$.pipe( - select(uiStateMouseoverUserLandmark) - ) - - const onHoverLandmark$ = this.store$.pipe( - select(uiStateMouseOverLandmarkSelector) - ).pipe( - map(landmark => { - if (landmark === null) { return null } - const idx = Number(landmark.replace('label=', '')) - if (isNaN(idx)) { - this.log.warn(`Landmark index could not be parsed as a number: ${landmark}`) - return { - landmarkName: idx, - } - } - }), - ) + const onHoverUserLandmark$ = NEVER + // this.store$.pipe( + // select(uiStateMouseoverUserLandmark) + // ) + + const onHoverLandmark$ = NEVER + // this.store$.pipe( + // select(uiStateMouseOverLandmarkSelector) + // ).pipe( + // map(landmark => { + // if (landmark === null) { return null } + // const idx = Number(landmark.replace('label=', '')) + // if (isNaN(idx)) { + // this.log.warn(`Landmark index could not be parsed as a number: ${landmark}`) + // return { + // landmarkName: idx, + // } + // } + // }), + // ) const onHoverSegments$ = this.store$.pipe( - select(uiStateMouseOverSegmentsSelector), + select(userInteraction.selectors.mousingOverRegions), // TODO fix aux mesh filtering @@ -59,7 +61,7 @@ export class MouseHoverDirective { // ? arr.filter(({ segment }) => { // // if segment is not a string (i.e., not labelIndexId) return true // if (typeof segment !== 'string') { return true } - // const { labelIndex } = deserialiseParcRegionId(segment) + // const { label: labelIndex } = deserializeSegment(segment) // return parcellationSelected.auxillaryMeshIndices.indexOf(labelIndex) < 0 // }) // : arr), @@ -70,8 +72,8 @@ export class MouseHoverDirective { const mergeObs = merge( onHoverSegments$.pipe( distinctUntilChanged(), - map(segments => { - return { segments } + map(regions => { + return { regions } }), ), onHoverAnnotation$.pipe( @@ -101,7 +103,7 @@ export class MouseHoverDirective { map(arr => { let returnObj = { - segments: null, + regions: null, annotation: null, landmark: null, userLandmark: null, diff --git a/src/mouseoverModule/type.ts b/src/mouseoverModule/type.ts index 07c4312fa7896c53a7f4a8ca58b842e3cd88dc59..38033f903e9047b4164c90137564a19d0f75ae86 100644 --- a/src/mouseoverModule/type.ts +++ b/src/mouseoverModule/type.ts @@ -1,13 +1,5 @@ import { TRegionSummary } from "src/util/siibraApiConstants/types"; -export type TMouseOverSegment = { - layer: { - name: string - } - segmentId: number - segment: TRegionSummary | string // if cannot decode, then segment will be {ngId}#{labelIndex} -} - export type TMouseOverVtkLandmark = { landmarkName: string } \ No newline at end of file diff --git a/src/mouseoverModule/util.ts b/src/mouseoverModule/util.ts index ed4fbe50f42c9580f554e19b6e98678438c1b038..0811db26a592492f396f27f1d61cae1b4c7e2cb6 100644 --- a/src/mouseoverModule/util.ts +++ b/src/mouseoverModule/util.ts @@ -1,8 +1,8 @@ +import { SapiRegionModel } from "src/atlasComponents/sapi" import { IAnnotationGeometry } from "src/atlasComponents/userAnnotations/tools/type" -import { TMouseOverSegment } from "./type" export type TOnHoverObj = { - segments: TMouseOverSegment[] + regions: SapiRegionModel[] annotation: IAnnotationGeometry landmark: { landmarkName: number diff --git a/src/overwrite.scss b/src/overwrite.scss index c8e7e12e46e899f162b3085f1cd523be12835d24..790e436d35acc4e970127548ec060fa796b6a650 100644 --- a/src/overwrite.scss +++ b/src/overwrite.scss @@ -1,41 +1,7 @@ @use 'sass:math'; -iav-cmp-viewer-container -{ - - .mat-chip-list-wrapper - { - flex-wrap: nowrap; - } - - poly-update-cmp - { - .mat-chip-list-wrapper - { - flex-wrap: wrap; - } - } -} - -quick-tour-unit -{ - svg - { - pointer-events: none; - stroke-width: 3px; - stroke: rgb(255, 255, 255); - stroke-linecap: round; - stroke-linejoin: round; - } -} +$nsp: "sxplr"; -kg-ds-prv-regional-feature-view -{ - div - { - min-height: 20em; - } -} // no prefix @for $i from 1 through 12 { @@ -71,3 +37,245 @@ $media-map: ( } } } + +@for $i from 5 through 20 { + $fontsize: $i * 10; + .fs-#{$fontsize} { + font-size: $fontsize * 1%; + } +} + + +@for $i from 4 through 10 { + $tensvar: $i * 10; + .#{$nsp}-mxh-#{$tensvar}vh { + max-height: $tensvar * 1vh; + } + .#{$nsp}-mxw-#{$tensvar}vw { + max-width: $tensvar * 1vw; + } +} + +$overflow-directive: hidden, scroll, auto, visible; +@each $directive in $overflow-directive { + .#{$nsp}-of-x-#{$directive} { + overflow-x: $directive!important; + } + .#{$nsp}-of-y-#{$directive} { + overflow-y: $directive!important; + } + .#{$nsp}-of-#{$directive} { + overflow: $directive!important; + } +} + +@for $scale from 5 through 10 { + + $scale-var: $scale * 10; + .#{$nsp}-scale-#{$scale-var} + { + transform: scale($scale * 0.1); + } +} + +$transform-origin-vars: "left-center", "center"; +$transform-origin-maps: ( + "left-center": 0% 50%, + "center": 50% 50% +); + +@each $var in $transform-origin-vars { + .transform-origin-#{$var} { + transform-origin: map-get($transform-origin-maps, $var); + } +} + +@for $unit from 5 through 10 { + $width: $unit * 10; + .w-#{$width} { + width: $width * 1%; + } + .#{$nsp}-w-#{$width} { + width: $width * 1%; + } + .h-#{$width} { + width: $width * 1%; + } + .#{$nsp}-h-#{$width} { + height: $width * 1%; + } +} + +@for $zlvl from 0 through 10 { + .#{$nsp}-z-#{$zlvl} { + z-index: $zlvl; + } +} + +@for $unit from 0 through 10 { + .#{$nsp}-pt-#{$unit}{ + padding-top: $unit * 0.5rem!important; + } + .#{$nsp}-pb-#{$unit}{ + padding-bottom: $unit * 0.5rem!important; + } + .#{$nsp}-pl-#{$unit}{ + padding-left: $unit * 0.5rem!important; + } + .#{$nsp}-pr-#{$unit}{ + padding-right: $unit * 0.5rem!important; + } + .#{$nsp}-p-#{$unit}{ + padding: $unit * 0.5rem!important; + } + + .#{$nsp}-mt-#{$unit}{ + margin-top: $unit * 0.5rem!important; + } + .#{$nsp}-mb-#{$unit}{ + margin-bottom: $unit * 0.5rem!important; + } + .#{$nsp}-ml-#{$unit}{ + margin-left: $unit * 0.5rem!important; + } + .#{$nsp}-mr-#{$unit}{ + margin-right: $unit * 0.5rem!important; + } + .#{$nsp}-m-#{$unit}{ + margin: $unit * 0.5rem!important; + } +} + +.#{$nsp}-mt-a { + margin-top: auto!important; +} +.#{$nsp}-mb-a { + margin-bottom: auto!important; +} + +$display-vars: none, block, inline-block, flex, inline-flex; +@each $display-var in $display-vars { + .d-#{$display-var} + { + display: $display-var!important; + } + .#{$nsp}-d-#{$display-var} + { + display: $display-var!important; + } +} + +$align-items-vars: center, stretch, start; +@each $align-items-var in $align-items-vars { + .align-items-#{$align-items-var} { + align-items: $align-items-var; + } + + .#{$nsp}-align-items-#{$align-items-var} { + align-items: $align-items-var; + } +} + +$justify-content-vars: end, center, space-between; +@each $justify-content-var in $justify-content-vars { + .#{$nsp}-justify-content-#{$justify-content-var} { + justify-content: $justify-content-var; + } +} + +.#{$nsp}-m-a { + margin: auto; +} + +.#{$nsp}-muted { + opacity: 0.75; +} + +.#{$nsp}-very-muted { + opacity: 0.5; +} + +.#{$nsp}-extra-muted { + opacity: 0.25; +} + +$position-vars: relative, absolute; +@each $position-var in $position-vars { + .#{$nsp}-position-#{$position-var} { + position: $position-var; + } +} + +.#{$nsp}-bg-none +{ + background: none!important; +} + +.#{$nsp}-box-shadow-none +{ + box-shadow: none!important; +} + + +$white-space-vars: nowrap; +@each $white-space-var in $white-space-vars { + .#{$nsp}-white-space-#{$white-space-var} + { + white-space: $white-space-var!important; + } +} + +$pointer-events-vars: all, none; +@each $pointer-events-var in $pointer-events-vars { + .#{$nsp}-pe-#{$pointer-events-var} + { + pointer-events: $pointer-events-var!important; + } +} + +$width-pc-vars: 100; +@each $width-pc-var in $width-pc-vars { + .#{$nsp}-w-#{$width-pc-var} { + width: $width-pc-var * 1%; + } +} + +.#{$nsp}-border +{ + border-width: 1px; +} + +// flex +$flex-wrap-vars: nowrap, wrap, wrap-reverse; +@each $flex-wrap-var in $flex-wrap-vars { + .#{$nsp}-flex-wrap-#{$flex-wrap-var} { + flex-wrap: $flex-wrap-var; + } +} +$flex-directions: row,column; +@each $flex-direction in $flex-directions { + .#{$nsp}-flex-#{$flex-direction} { + flex-direction: $flex-direction; + } +} +.#{$nsp}-flex-var { + flex: 1 1 0px; +} + +.#{$nsp}-flex-static { + flex: 0 0 auto; +} + +.#{$nsp}-blink +{ + animation: blink 500ms ease-in-out infinite alternate; +} + +@keyframes blink { + 0% { + opacity: 0.8; + } + 100% { + opacity: 0.5; + } +} diff --git a/src/plugin/MIGRATION.md b/src/plugin/MIGRATION.md new file mode 100644 index 0000000000000000000000000000000000000000..cb162d8d0a680cd2dae7a855c4639e80c29e75e5 --- /dev/null +++ b/src/plugin/MIGRATION.md @@ -0,0 +1,31 @@ +# Migrate from siibra-explorer < 2.7.0 + +Plugin within siibra-explorer existed since before `pre-0.2.0`. We changed the way plugin works on `siibra-explorer==2.7.0`. + +## Why + +In siibra-explorer < 2.7.0, the HTML, JS are rendered directly in the same frame as siibra-explorer. + +Whilst this approach provided a lot of flexibility for the plugin, it also introduced a lot of points of failures and/or non-optimal practices. + +For example, the objects passed to the plugin was not always structureClone'd. This meant that plugins which mutate these objects could cause issues difficult to debug. + +Another example is that plugin authors often have to write HTML and JS specificially to interact with siibra-explorer. These code snippets often cannot be reused (since they expect a globally defined `interactiveViewer` object to exist.) + +Additionally, the previous system necessitates the running of arbitary JS code, which can be a security vulnerability. + +## The new system + +The plugin now runs in an iframe, and the data are passed between `siibra-explorer` and the plugin via [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). This address all/most of the three concerns above: + +- objects passed will always be a clone (per `postMessage` spec). This allows plugin authors to do as their heart content with the received data, and it will not affect the viewer instance + +- plugin authors will provide a valid HTML (rather than HTML fragment). It can be rendered independently without `siibra-explorer`.[1] + +- any arbitary code from the plugin is sandboxed in the iframe, and should not interfere with `siibra-explorer`. This does **not** completely eliminate potential security threats: + + - Best practices still needs to be followed to harden the security (e.g. use `sandbox` attribute (WIP)) + + - Existing browser vulnerabilities, which the browser vendors have much greater resources and incentive to provide a fix. + +[1] Most modern browser are quite forgiving when it comes to rendering HTML. They could often render partial/invalid HTML. We still believe having spec compliant HTML is a good practice. diff --git a/src/plugin/README.md b/src/plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8656ab3155dcd446795a36f562a66e3d06761eb1 --- /dev/null +++ b/src/plugin/README.md @@ -0,0 +1,49 @@ +# Plugins + +:warning: the API in this document refer to `siibra-explorer>=2.7.0`. For migration guide/rationale, please see [MIGRATION.md](./MIGRATION.md) + +siibra-explorer provides a plugin system, which allow a third party application to interact with siibra-explorer. + +## Quickstart + +### manifest + +The plugin need to expose a manifest json file. The manifest file needs to have the following properties: + +```json +{ + "iframeUrl": "<iframeUrl>", + "name": "<name>", + "siibra-explorer": "<siibra-explorer>" +} +``` + +| property | required | desc | +| --- | --- | --- | +| `iframeUrl` | true | points to the html where the iframe is located. If does not start with `https?://`, siibra-explorer will try to resolve it relative to the absolute path of manifest. | +| `name` | true | name of the plugin | +| `siibra-explorer` | true | the version siibra-explorer this plugin is targetting. Should be >= 2.7.0. n.b. currently this entry is partially implemented, and any truthy value is sufficient. + | + + +<!-- TBD --> + +## Architecture + +The plugin needs to provide a HTML page, served over HTTP. This will be embedded into siibra-explorer as an iframe. + +All communications between siibra-explorer and plugin will occur via the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). + +## Lifecycle + +`handshake.init` (up to 10x attempts, 1sec debounce) -> `{broadcast|request}` -> `handshake.exit` (NYI) + +Please note that the `handshake.init` needs to be responded to, *before* any other messages are sent. + +## API References + +[handshake API](./handshake.md) + +[broadcast API](./broadcast.md) + +[request API](./request.md) diff --git a/src/plugin/atlasViewer.pluginService.service.spec.ts b/src/plugin/atlasViewer.pluginService.service.spec.ts deleted file mode 100644 index 41e342b8bf77d8941eb731554972b71217e75b73..0000000000000000000000000000000000000000 --- a/src/plugin/atlasViewer.pluginService.service.spec.ts +++ /dev/null @@ -1,309 +0,0 @@ -import { CommonModule } from "@angular/common" -import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing" -import { NgModule } from "@angular/core" -import { async, fakeAsync, TestBed, tick } from "@angular/core/testing" -import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { ComponentsModule } from "src/components" -import { DialogService } from "src/services/dialogService.service" -import { selectorPluginCspPermission } from "src/services/state/userConfigState.helper" -import { AngularMaterialModule } from "src/sharedModules" -import { PureContantService } from "src/util" -import { APPEND_SCRIPT_TOKEN, REMOVE_SCRIPT_TOKEN } from "src/util/constants" -import { WidgetModule, WidgetServices } from "src/widget" -import { PluginServices } from "./atlasViewer.pluginService.service" -import { PluginModule } from "./plugin.module" -import { PluginUnit } from "./pluginUnit/pluginUnit.component" - -const MOCK_PLUGIN_MANIFEST = { - name: 'fzj.xg.MOCK_PLUGIN_MANIFEST', - templateURL: 'http://localhost:10001/template.html', - scriptURL: 'http://localhost:10001/script.js' -} - -const spyfn = { - appendSrc: jasmine.createSpy('appendSrc') -} - - - -describe('> atlasViewer.pluginService.service.ts', () => { - describe('> PluginServices', () => { - - let pluginService: PluginServices - let httpMock: HttpTestingController - let mockStore: MockStore - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - AngularMaterialModule, - CommonModule, - WidgetModule, - PluginModule, - HttpClientTestingModule, - ComponentsModule, - ], - providers: [ - provideMockStore(), - PluginServices, - { - provide: APPEND_SCRIPT_TOKEN, - useValue: spyfn.appendSrc - }, - { - provide: REMOVE_SCRIPT_TOKEN, - useValue: () => Promise.resolve() - }, - { - provide: DialogService, - useValue: { - getUserConfirm: () => Promise.resolve() - } - }, - { - provide: PureContantService, - useValue: { - backendUrl: `http://localhost:3000/` - } - } - ] - }).compileComponents().then(() => { - - httpMock = TestBed.inject(HttpTestingController) - pluginService = TestBed.inject(PluginServices) - mockStore = TestBed.inject(MockStore) - pluginService.pluginViewContainerRef = { - createComponent: () => { - return { - onDestroy: () => {}, - instance: { - elementRef: { - nativeElement: { - append: () => {} - } - } - } - } - } - } as any - - httpMock.expectOne('http://localhost:3000/plugins/manifests').flush('[]') - - const widgetService = TestBed.inject(WidgetServices) - /** - * widget service floatingcontainer not inst in this circumstance - * TODO fix widget service tests importing widget service are not as flaky - */ - widgetService.addNewWidget = () => { - return {} as any - } - }) - })) - - afterEach(() => { - spyfn.appendSrc.calls.reset() - const ctrl = TestBed.inject(HttpTestingController) - ctrl.verify() - }) - - it('> service can be inst', () => { - expect(pluginService).toBeTruthy() - }) - - it('expectOne is working as expected', done => { - - pluginService.fetch('test') - .then(text => { - expect(text).toEqual('bla') - done() - }) - httpMock.expectOne('test').flush('bla') - - }) - - /** - * need to consider user confirmation on csp etc - */ - describe('#launchPlugin', () => { - - beforeEach(() => { - mockStore.overrideSelector(selectorPluginCspPermission, { value: false }) - }) - - describe('> basic fetching functionality', () => { - it('> fetches templateURL and scriptURL properly', fakeAsync(() => { - - pluginService.launchPlugin({...MOCK_PLUGIN_MANIFEST}) - - tick(100) - - const mockTemplate = httpMock.expectOne(MOCK_PLUGIN_MANIFEST.templateURL) - mockTemplate.flush('hello world') - - tick(100) - - expect(spyfn.appendSrc).toHaveBeenCalledTimes(1) - expect(spyfn.appendSrc).toHaveBeenCalledWith(MOCK_PLUGIN_MANIFEST.scriptURL) - - })) - - it('> template overrides templateURL', fakeAsync(() => { - pluginService.launchPlugin({ - ...MOCK_PLUGIN_MANIFEST, - template: '' - }) - - tick(20) - httpMock.expectNone(MOCK_PLUGIN_MANIFEST.templateURL) - })) - - it('> script with scriptURL throws', done => { - pluginService.launchPlugin({ - ...MOCK_PLUGIN_MANIFEST, - script: '', - scriptURL: null - }) - .then(() => { - /** - * should not pass - */ - expect(true).toEqual(false) - }) - .catch(e => { - done() - }) - - /** - * http call will not be made, as rejection happens by Promise.reject, while fetch call probably happens at the next event cycle - */ - httpMock.expectNone(MOCK_PLUGIN_MANIFEST.templateURL) - }) - - describe('> user permission', () => { - let userConfirmSpy: jasmine.Spy - let readyPluginSpy: jasmine.Spy - let cspManifest = { - ...MOCK_PLUGIN_MANIFEST, - csp: { - 'connect-src': [`'unsafe-eval'`] - } - } - afterEach(() => { - userConfirmSpy.calls.reset() - readyPluginSpy.calls.reset() - }) - beforeEach(() => { - readyPluginSpy = spyOn(pluginService, 'readyPlugin').and.callFake(() => Promise.reject()) - const dialogService = TestBed.inject(DialogService) - userConfirmSpy = spyOn(dialogService, 'getUserConfirm') - }) - - describe('> if user permission has been given', () => { - beforeEach(fakeAsync(() => { - mockStore.overrideSelector(selectorPluginCspPermission, { value: true }) - userConfirmSpy.and.callFake(() => Promise.reject()) - pluginService.launchPlugin({ - ...cspManifest - }).catch(() => { - /** - * expecting to throw because call fake returning promise.reject in beforeEach - */ - }) - tick(20) - })) - it('> will not ask for permission', () => { - expect(userConfirmSpy).not.toHaveBeenCalled() - }) - - it('> will call ready plugin', () => { - expect(readyPluginSpy).toHaveBeenCalled() - }) - }) - - describe('> if user permission has not yet been given', () => { - beforeEach(() => { - mockStore.overrideSelector(selectorPluginCspPermission, { value: false }) - }) - describe('> user permission', () => { - beforeEach(fakeAsync(() => { - pluginService.launchPlugin({ - ...cspManifest - }).catch(() => { - /** - * expecting to throw because call fake returning promise.reject in beforeEach - */ - }) - tick(40) - })) - it('> will be asked for', () => { - expect(userConfirmSpy).toHaveBeenCalled() - }) - }) - - describe('> if user accepts', () => { - beforeEach(fakeAsync(() => { - userConfirmSpy.and.callFake(() => Promise.resolve()) - - pluginService.launchPlugin({ - ...cspManifest - }).catch(() => { - /** - * expecting to throw because call fake returning promise.reject in beforeEach - */ - }) - })) - it('> calls /POST user/pluginPermissions', () => { - httpMock.expectOne({ - method: 'POST', - url: 'http://localhost:3000/user/pluginPermissions' - }) - }) - }) - - describe('> if user declines', () => { - - beforeEach(fakeAsync(() => { - userConfirmSpy.and.callFake(() => Promise.reject()) - - pluginService.launchPlugin({ - ...cspManifest - }).catch(() => { - /** - * expecting to throw because call fake returning promise.reject in beforeEach - */ - }) - })) - it('> calls /POST user/pluginPermissions', () => { - httpMock.expectNone({ - method: 'POST', - url: 'http://localhost:3000/user/pluginPermissions' - }) - }) - }) - }) - }) - }) - - describe('> racing slow connection when launching plugin', () => { - it('> when template/script has yet been fetched, repeated launchPlugin should not result in repeated fetching', fakeAsync(() => { - - expect(pluginService.pluginIsLaunching(MOCK_PLUGIN_MANIFEST.name)).toBeFalsy() - expect(pluginService.pluginHasLaunched(MOCK_PLUGIN_MANIFEST.name)).toBeFalsy() - pluginService.launchPlugin({...MOCK_PLUGIN_MANIFEST}) - pluginService.launchPlugin({...MOCK_PLUGIN_MANIFEST}) - tick(20) - const req = httpMock.expectOne(MOCK_PLUGIN_MANIFEST.templateURL) - req.flush('baba') - tick(20) - expect(spyfn.appendSrc).toHaveBeenCalledTimes(1) - - expect( - pluginService.pluginIsLaunching(MOCK_PLUGIN_MANIFEST.name) || - pluginService.pluginHasLaunched(MOCK_PLUGIN_MANIFEST.name) - ).toBeTruthy() - })) - }) - - }) - }) -}) diff --git a/src/plugin/atlasViewer.pluginService.service.ts b/src/plugin/atlasViewer.pluginService.service.ts deleted file mode 100644 index 668027764cf639fe3be327119a12fb4140352f08..0000000000000000000000000000000000000000 --- a/src/plugin/atlasViewer.pluginService.service.ts +++ /dev/null @@ -1,414 +0,0 @@ -import { HttpClient } from '@angular/common/http' -import { ComponentFactory, ComponentFactoryResolver, Injectable, ViewContainerRef, Inject, SecurityContext } from "@angular/core"; -import { PLUGINSTORE_ACTION_TYPES } from "src/services/state/pluginState.helper"; -import { PluginUnit } from "./pluginUnit/pluginUnit.component"; -import { select, Store } from "@ngrx/store"; -import { BehaviorSubject, from, merge, Observable, of } from "rxjs"; -import { catchError, filter, map, mapTo, shareReplay, switchMap, switchMapTo, take, tap } from "rxjs/operators"; -import { LoggingService } from 'src/logging'; -import { WidgetUnit, WidgetServices } from "src/widget"; -import { APPEND_SCRIPT_TOKEN, REMOVE_SCRIPT_TOKEN, getHttpHeader } from 'src/util/constants'; -import { PluginFactoryDirective } from './pluginFactory.directive'; -import { selectorPluginCspPermission } from 'src/services/state/userConfigState.helper'; -import { DialogService } from 'src/services/dialogService.service'; -import { DomSanitizer } from '@angular/platform-browser'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { PureContantService } from 'src/util'; - -const requiresReloadMd = `\n\n***\n\n**warning**: interactive atlas viewer **will** be reloaded in order for the change to take effect.` - -class PluginHandler { - public onShutdown: (callback: () => void) => void - public blink: (sec?: number) => void - public shutdown: () => void - - public initState?: any - public initStateUrl?: string - - public setInitManifestUrl: (url: string|null) => void - - public setProgressIndicator: (progress: number) => void -} - -export const registerPluginFactoryDirectiveFactory = (pSer: PluginServices) => { - return (pFactoryDirective: PluginFactoryDirective) => { - pSer.loadExternalLibraries = pFactoryDirective.loadExternalLibraries.bind(pFactoryDirective) - pSer.unloadExternalLibraries = pFactoryDirective.unloadExternalLibraries.bind(pFactoryDirective) - pSer.pluginViewContainerRef = pFactoryDirective.viewContainerRef - } -} - -@Injectable({ - providedIn : 'root', -}) - -export class PluginServices { - - public pluginHandlersMap: Map<string, PluginHandler> = new Map() - - public loadExternalLibraries: (libraries: string[]) => Promise<any> = () => Promise.reject(`fail to overwritten`) - public unloadExternalLibraries: (libraries: string[]) => void = () => { throw new Error(`failed to be overwritten`) } - - public fetchedPluginManifests: IPluginManifest[] = [] - public pluginViewContainerRef: ViewContainerRef - - private pluginUnitFactory: ComponentFactory<PluginUnit> - public minimisedPlugins$: Observable<Set<string>> - - /** - * TODO remove polyfil and convert all calls to this.fetch to http client - */ - public fetch: (url: string, httpOption?: any) => Promise<any> = (url, httpOption = {}) => this.http.get(url, httpOption).toPromise() - - constructor( - private widgetService: WidgetServices, - private cfr: ComponentFactoryResolver, - private store: Store<any>, - private dialogService: DialogService, - private snackbar: MatSnackBar, - private http: HttpClient, - private log: LoggingService, - private sanitizer: DomSanitizer, - private constantSvc: PureContantService, - @Inject(APPEND_SCRIPT_TOKEN) private appendSrc: (src: string) => Promise<HTMLScriptElement>, - @Inject(REMOVE_SCRIPT_TOKEN) private removeSrc: (src: HTMLScriptElement) => void, - ) { - - this.pluginUnitFactory = this.cfr.resolveComponentFactory( PluginUnit ) - - /** - * TODO convert to rxjs streams, instead of Promise.all - */ - const pluginManifestsUrl = `${this.constantSvc.backendUrl}plugins/manifests` - - this.http.get<IPluginManifest[]>(pluginManifestsUrl, { - responseType: 'json', - headers: getHttpHeader(), - }).subscribe( - arr => this.fetchedPluginManifests = arr, - this.log.error, - ) - - this.minimisedPlugins$ = merge( - of(new Set()), - this.widgetService.minimisedWindow$, - ).pipe( - map(set => { - const returnSet = new Set<string>() - for (const [pluginName, wu] of this.mapPluginNameToWidgetUnit) { - if (set.has(wu)) { - returnSet.add(pluginName) - } - } - return returnSet - }), - shareReplay(1), - ) - - this.launchedPlugins$ = new BehaviorSubject(new Set()) - } - - public launchNewWidget = (manifest) => this.launchPlugin(manifest) - .then(handler => { - this.orphanPlugins.add(manifest) - handler.onShutdown(() => { - this.orphanPlugins.delete(manifest) - }) - }) - - public readyPlugin(plugin: IPluginManifest): Promise<any> { - const isDefined = input => typeof input !== 'undefined' && input !== null - if (!isDefined(plugin.scriptURL)) { - return Promise.reject(`inline script has been deprecated. use scriptURL instead`) - } - if (isDefined(plugin.template)) { - return Promise.resolve() - } - if (plugin.templateURL) { - return this.fetch(plugin.templateURL, {responseType: 'text'}) - .then(template => { - plugin.template = template - }) - } - return Promise.reject('both template and templateURL are not defined') - } - - private launchedPlugins: Set<string> = new Set() - public launchedPlugins$: BehaviorSubject<Set<string>> - public pluginHasLaunched(pluginName: string) { - return this.launchedPlugins.has(pluginName) - } - public addPluginToLaunchedSet(pluginName: string) { - this.launchedPlugins.add(pluginName) - this.launchedPlugins$.next(this.launchedPlugins) - } - public removePluginFromLaunchedSet(pluginName: string) { - this.launchedPlugins.delete(pluginName) - this.launchedPlugins$.next(this.launchedPlugins) - } - - public pluginIsLaunching(pluginName: string) { - return this.launchingPlugins.has(pluginName) - } - public addPluginToIsLaunchingSet(pluginName: string) { - this.launchingPlugins.add(pluginName) - } - public removePluginFromIsLaunchingSet(pluginName: string) { - this.launchingPlugins.delete(pluginName) - } - - private mapPluginNameToWidgetUnit: Map<string, WidgetUnit> = new Map() - - public pluginIsMinimised(pluginName: string) { - return this.widgetService.isMinimised( this.mapPluginNameToWidgetUnit.get(pluginName) ) - } - - private launchingPlugins: Set<string> = new Set() - public orphanPlugins: Set<IPluginManifest> = new Set() - - public async revokePluginPermission(pluginKey: string) { - const createRevokeMd = (pluginKey: string) => `You are about to revoke the permission given to ${pluginKey}.${requiresReloadMd}` - - try { - await this.dialogService.getUserConfirm({ - markdown: createRevokeMd(pluginKey) - }) - - this.http.delete( - `${this.constantSvc.backendUrl}user/pluginPermissions/${encodeURIComponent(pluginKey)}`, - { - headers: getHttpHeader() - } - ).subscribe( - () => { - window.location.reload() - }, - err => { - this.snackbar.open(`Error revoking plugin permission ${err.toString()}`, 'Dismiss') - } - ) - } catch (_e) { - /** - * user cancelled workflow - */ - } - } - - public async launchPlugin(plugin: IPluginManifest): Promise<PluginHandler> { - if (this.pluginIsLaunching(plugin.name)) { - // plugin launching please be patient - // TODO add visual feedback - return - } - if ( this.pluginHasLaunched(plugin.name)) { - // plugin launched - // TODO add visual feedback - - // if widget window is minimized, maximize it - - const wu = this.mapPluginNameToWidgetUnit.get(plugin.name) - if (this.widgetService.isMinimised(wu)) { - this.widgetService.unminimise(wu) - } else { - this.widgetService.minimise(wu) - } - return - } - - this.addPluginToIsLaunchingSet(plugin.name) - - const { csp, displayName, name = '', version = 'latest' } = plugin - const pluginKey = `${name}::${version}` - const createPermissionMd = ({ csp, name, version }) => { - const sanitize = val => this.sanitizer.sanitize(SecurityContext.HTML, val) - const getCspRow = ({ key }) => { - return `| ${sanitize(key)} | ${csp[key].map(v => '`' + sanitize(v) + '`').join(',')} |` - } - return `**${sanitize(displayName || name)}** version **${sanitize(version)}** requires additional permission from you to run:\n\n| permission | detail |\n| --- | --- |\n${Object.keys(csp).map(key => getCspRow({ key })).join('\n')}${requiresReloadMd}` - } - - await new Promise((rs, rj) => { - this.store.pipe( - select(selectorPluginCspPermission, { key: pluginKey }), - take(1), - switchMap(userAgreed => { - if (userAgreed.value) return of(true) - - /** - * check if csp exists - */ - if (!csp || Object.keys(csp).length === 0) { - return of(true) - } - /** - * TODO: check do not ask status - */ - return from( - this.dialogService.getUserConfirm({ - markdown: createPermissionMd({ csp, name, version }) - }) - ).pipe( - mapTo(true), - catchError(() => of(false)), - filter(v => !!v), - switchMapTo( - this.http.post(`${this.constantSvc.backendUrl}user/pluginPermissions`, - { [pluginKey]: csp }, - { - responseType: 'json', - headers: getHttpHeader() - }) - ), - tap(() => { - window.location.reload() - }), - mapTo(false) - ) - }), - take(1), - ).subscribe( - val => val ? rs(null) : rj(`val is falsy`), - err => rj(err) - ) - }) - - await this.readyPlugin(plugin) - - /** - * catch when pluginViewContainerRef as not been overwritten? - */ - if (!this.pluginViewContainerRef) { - throw new Error(`pluginViewContainerRef not populated`) - } - const pluginUnit = this.pluginViewContainerRef.createComponent( this.pluginUnitFactory ) - /* TODO in v0.2, I used: - - const template = document.createElement('div') - template.insertAdjacentHTML('afterbegin',template) - - // reason was: - // changed from innerHTML to insertadjacenthtml to accomodate angular elements ... not too sure about the actual ramification - - */ - - const handler = new PluginHandler() - this.pluginHandlersMap.set(plugin.name, handler) - - /** - * define the handler properties prior to appending plugin script - * so that plugin script can access properties w/o timeout - */ - handler.initState = plugin.initState - ? plugin.initState - : null - - handler.initStateUrl = plugin.initStateUrl - ? plugin.initStateUrl - : null - - handler.setInitManifestUrl = (url) => this.store.dispatch({ - type : PLUGINSTORE_ACTION_TYPES.SET_INIT_PLUGIN, - manifest : { - name : plugin.name, - initManifestUrl : url, - }, - }) - - const shutdownCB = [ - () => { - this.removePluginFromLaunchedSet(plugin.name) - }, - ] - - handler.onShutdown = (cb) => { - if (typeof cb !== 'function') { - this.log.warn('onShutdown requires the argument to be a function') - return - } - shutdownCB.push(cb) - } - - const scriptEl = await this.appendSrc(plugin.scriptURL) - - handler.onShutdown(() => this.removeSrc(scriptEl)) - - const template = document.createElement('div') - template.insertAdjacentHTML('afterbegin', plugin.template) - pluginUnit.instance.elementRef.nativeElement.append( template ) - - const widgetCompRef = this.widgetService.addNewWidget(pluginUnit, { - state : 'floating', - exitable : true, - persistency: plugin.persistency, - title : plugin.displayName || plugin.name, - }) - - this.addPluginToLaunchedSet(plugin.name) - this.removePluginFromIsLaunchingSet(plugin.name) - - this.mapPluginNameToWidgetUnit.set(plugin.name, widgetCompRef.instance) - - const unsubscribeOnPluginDestroy = [] - - // TODO deprecate sec - handler.blink = (_sec?: number) => { - widgetCompRef.instance.blinkOn = true - } - - handler.setProgressIndicator = (val) => widgetCompRef.instance.progressIndicator = val - - handler.shutdown = () => { - widgetCompRef.instance.exit() - } - - handler.onShutdown(() => { - unsubscribeOnPluginDestroy.forEach(s => s.unsubscribe()) - this.pluginHandlersMap.delete(plugin.name) - this.mapPluginNameToWidgetUnit.delete(plugin.name) - }) - - pluginUnit.onDestroy(() => { - while (shutdownCB.length > 0) { - shutdownCB.pop()() - } - }) - - return handler - } - - public async addPluginViaManifestUrl(manifestUrl: string){ - try { - const json = await this.fetch(manifestUrl) - this.fetchedPluginManifests = [ - ...this.fetchedPluginManifests, - json - ] - } catch (e) { - throw new Error(e.statusText) - } - } -} - -export interface IPluginManifest { - name?: string - version?: string - displayName?: string - templateURL?: string - template?: string - scriptURL?: string - script?: string - initState?: any - initStateUrl?: string - persistency?: boolean - - description?: string - desc?: string - - homepage?: string - authors?: string - - csp?: { - 'connect-src'?: string[] - 'script-src'?: string[] - } -} diff --git a/src/plugin/broadcast.md b/src/plugin/broadcast.md new file mode 100644 index 0000000000000000000000000000000000000000..bcc96cd4add3e291b62df5557c1afdc0f9a9501d --- /dev/null +++ b/src/plugin/broadcast.md @@ -0,0 +1,64 @@ +# Broadcasting API + +Broadcasting messages are sent under two circumstances: + +- the state of the viewer changed, initiated by any source (user, plugin etc). Sent to all active plugin clients. + +- immediately after the plugin client acknowledged `handshake.init` to the specific client. This is so that the client can get the current state of the viewer. + +Broadcasting messages never expects a response (and thus will never contain and `id` attribute) + +<!-- the API reference below are auto generated by generateTypes.js --> +<!-- do not edit, as the edit will be overwritten by the auto generation --> + +## API + +### `sxplr.on.atlasSelected` + +- payload + + ```ts + SapiAtlasModel + ``` + + + +### `sxplr.on.templateSelected` + +- payload + + ```ts + SapiSpaceModel + ``` + + + +### `sxplr.on.parcellationSelected` + +- payload + + ```ts + SapiParcellationModel + ``` + + + +### `sxplr.on.allRegions` + +- payload + + ```ts + SapiRegionModel[] + ``` + + + +### `sxplr.on.regionsSelected` + +- payload + + ```ts + SapiRegionModel[] + ``` + + diff --git a/src/plugin/const.ts b/src/plugin/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..474bc36bf2cc119f845074735cc3073368db6a13 --- /dev/null +++ b/src/plugin/const.ts @@ -0,0 +1,16 @@ +import { InjectionToken } from "@angular/core" + +const PLUGIN_SRC_KEY = "x-plugin-portal-src" + +export function setPluginSrc(src: string, record: Record<string, unknown> = {}){ + return { + ...record, + [PLUGIN_SRC_KEY]: src + } +} + +export function getPluginSrc(record: Record<string, string> = {}){ + return record[PLUGIN_SRC_KEY] +} + +export const SET_PLUGIN_NAME = new InjectionToken('SET_PLUGIN_NAME') diff --git a/src/plugin/generateTypes.js b/src/plugin/generateTypes.js new file mode 100644 index 0000000000000000000000000000000000000000..52f69d8f99921f02bbaf4f3949fdc212a2cd4203 --- /dev/null +++ b/src/plugin/generateTypes.js @@ -0,0 +1,96 @@ +const ts = require('typescript') +const fs = require('fs') +const path = require('path') +const { promisify } = require('util') +const asyncReadFile = promisify(fs.readFile) +const asyncWriteFile = promisify(fs.writeFile) +const { processTypeAliasDeclaration, processRequestTypeAlias } = require('./tsUtil') + + +const typeAliasDeclarationMap = new Map() +const pathToApiService = path.join(__dirname, '../api/service.ts') +const NAMESPACE = `sxplr` +const filenames = { + handshake: path.join(__dirname, './handshake.md'), + broadcast: path.join(__dirname, './broadcast.md'), + request: path.join(__dirname, './request.md'), +} + +const puplateBroadCast = async broadcastNode => { + + if (!broadcastNode) throw new Error(`broadcastNode must be passed!`) + + const src = await asyncReadFile(filenames.broadcast, 'utf-8') + const output = processTypeAliasDeclaration(broadcastNode) + + let outputText = `` + for (const key in output) { + outputText += ` + +### \`${NAMESPACE}.on.${key}\` + +- payload + + \`\`\`ts + ${output[key]} + \`\`\` + +` + } + const newData = src.replace(/## API(.|\n)+/, s => `## API${outputText}\n`) + + await asyncWriteFile(filenames.broadcast, newData, 'utf-8') +} + +const populateConversations = async (filename, node) => { + const src = await asyncReadFile(filename, 'utf-8') + const output = processRequestTypeAlias(node, typeAliasDeclarationMap) + + let outputText = `` + for (const key in output) { + outputText += ` +### \`${NAMESPACE}.${key}\` + +- request + + \`\`\`ts + ${output[key]['request']} + \`\`\` + +- response + + \`\`\`ts + ${output[key]['response']} + \`\`\` + +` + } + const newData = src.replace(/## API(.|\n)+/, s => `## API${outputText}`) + await asyncWriteFile(filename, newData, 'utf-8') +} + +const main = async () => { + const src = await asyncReadFile(pathToApiService, 'utf-8') + const node = ts.createSourceFile( + './x.ts', + src, + ts.ScriptTarget.Latest + ) + node.forEachChild(n => { + if (ts.SyntaxKind[n.kind] === "TypeAliasDeclaration") { + typeAliasDeclarationMap.set(n.name?.text, n) + } + if (n.name?.text === "BroadCastingApiEvents") { + puplateBroadCast(n) + } + if (n.name?.text === "HeartbeatEvents") { + populateConversations(filenames.handshake, n) + } + if (n.name?.text === "ApiBoothEvents") { + populateConversations(filenames.request, n) + } + }) + +} + +main() diff --git a/src/plugin/handshake.md b/src/plugin/handshake.md new file mode 100644 index 0000000000000000000000000000000000000000..a55a97d23d23215df31d6f4ac33142d4066330d9 --- /dev/null +++ b/src/plugin/handshake.md @@ -0,0 +1,22 @@ +# Handshake API + +Handshake messages are meant for siibra-explorer to probe if the plugin is alive and well (and also a way for the plugin to check if siibra-explorer is responsive) + +<!-- the API reference below are auto generated by generateTypes.js --> +<!-- do not edit, as the edit will be overwritten by the auto generation --> + +## API +### `sxplr.init` + +- request + + ```ts + null + ``` + +- response + + ```ts + {"name": string} + ``` + diff --git a/src/plugin/iframeSrc.pipe.ts b/src/plugin/iframeSrc.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..0bc0916b5edb23234dbc089239fad37f547d28bc --- /dev/null +++ b/src/plugin/iframeSrc.pipe.ts @@ -0,0 +1,23 @@ +import { Pipe, PipeTransform, SecurityContext } from "@angular/core"; +import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser"; + +@Pipe({ + name: 'iframeSrc', + pure: true +}) + +export class IFrameSrcPipe implements PipeTransform { + constructor(private domSanitizer: DomSanitizer){} + + transform(src: string): SafeResourceUrl { + // https://angular.io/guide/security#sanitization-and-security-contexts + // Sanitizing resource url isn't possible + // hence bypassing + return this.domSanitizer.bypassSecurityTrustResourceUrl( + this.domSanitizer.sanitize( + SecurityContext.URL, + src + ) + ) + } +} diff --git a/src/plugin/index.ts b/src/plugin/index.ts index bae5875b5d19c8b096188c6c8074e42a2ff14674..480c8fb59b4e00addf28512aede8cead1c33df70 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -1,9 +1,3 @@ -export { - IPluginManifest, - PluginServices, - registerPluginFactoryDirectiveFactory, -} from './atlasViewer.pluginService.service' - export { PluginModule } from './plugin.module' \ No newline at end of file diff --git a/src/plugin/plugin.module.ts b/src/plugin/plugin.module.ts index ae859bab6ea49c0ab4be9f1d2e2adb4956249dbe..ea1ec730ae3867562e47a66399e7fd5ecc531f17 100644 --- a/src/plugin/plugin.module.ts +++ b/src/plugin/plugin.module.ts @@ -1,14 +1,14 @@ import { CommonModule, DOCUMENT } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; import { NgModule } from "@angular/core"; import { LoggingModule } from "src/logging"; import { AngularMaterialModule } from "src/sharedModules"; import { UtilModule } from "src/util"; import { appendScriptFactory, APPEND_SCRIPT_TOKEN, removeScriptFactory, REMOVE_SCRIPT_TOKEN } from "src/util/constants"; -import { PluginServices, registerPluginFactoryDirectiveFactory } from "./atlasViewer.pluginService.service"; +import { IFrameSrcPipe } from "./iframeSrc.pipe"; import { PluginBannerUI } from "./pluginBanner/pluginBanner.component"; -import { PluginCspCtrlCmp } from "./pluginCsp/pluginCsp.component"; -import { PluginFactoryDirective, REGISTER_PLUGIN_FACTORY_DIRECTIVE } from "./pluginFactory.directive"; -import { PluginUnit } from "./pluginUnit/pluginUnit.component"; +import { PluginPortal } from "./pluginPortal/pluginPortal.component"; + @NgModule({ imports: [ @@ -16,27 +16,17 @@ import { PluginUnit } from "./pluginUnit/pluginUnit.component"; LoggingModule, UtilModule, AngularMaterialModule, + HttpClientModule, ], declarations: [ - PluginCspCtrlCmp, - PluginUnit, - PluginFactoryDirective, PluginBannerUI, + PluginPortal, + IFrameSrcPipe, ], exports: [ - PluginCspCtrlCmp, PluginBannerUI, - PluginUnit, - PluginFactoryDirective, ], providers: [ - - PluginServices, - { - provide: REGISTER_PLUGIN_FACTORY_DIRECTIVE, - useFactory: registerPluginFactoryDirectiveFactory, - deps: [ PluginServices ] - }, { provide: APPEND_SCRIPT_TOKEN, useFactory: appendScriptFactory, diff --git a/src/plugin/pluginBanner/pluginBanner.component.ts b/src/plugin/pluginBanner/pluginBanner.component.ts index 689f0aa32bfc4a7c16442bbc22e56c95d44a2b0d..df682e4cfd68a3cefac4a8b9952670cdcfb49bb0 100644 --- a/src/plugin/pluginBanner/pluginBanner.component.ts +++ b/src/plugin/pluginBanner/pluginBanner.component.ts @@ -1,8 +1,10 @@ -import { Component, ViewChild, TemplateRef } from "@angular/core"; -import { IPluginManifest, PluginServices } from "../atlasViewer.pluginService.service"; +import { Component, TemplateRef } from "@angular/core"; import { MatDialog } from "@angular/material/dialog"; import { environment } from 'src/environments/environment'; -import { MatSnackBar } from "@angular/material/snack-bar"; +import { PluginService } from "../service"; +import { PluginManifest } from "../types"; +import { combineLatest, Observable, Subject } from "rxjs"; +import { map, scan, startWith } from "rxjs/operators"; @Component({ selector : 'plugin-banner', @@ -16,28 +18,14 @@ export class PluginBannerUI { EXPERIMENTAL_FEATURE_FLAG = environment.EXPERIMENTAL_FEATURE_FLAG - @ViewChild('pluginInfoTmpl', { read: TemplateRef }) - private pluginInfoTmpl: TemplateRef<any> - constructor( - public pluginServices: PluginServices, + private svc: PluginService, private matDialog: MatDialog, - private matSnackbar: MatSnackBar, ) { } - public clickPlugin(plugin: IPluginManifest) { - this.pluginServices.launchPlugin(plugin) - } - - public showPluginInfo(manifest: IPluginManifest){ - this.matDialog.open( - this.pluginInfoTmpl, - { - data: manifest, - ariaLabel: `Additional information about a plugin` - } - ) + public launchPlugin(plugin: PluginManifest) { + this.svc.launchPlugin(plugin.iframeUrl) } public showTmpl(tmpl: TemplateRef<any>){ @@ -46,21 +34,25 @@ export class PluginBannerUI { }) } - public loadingThirdpartyPlugin = false - - public async addThirdPartyPlugin(manifestUrl: string) { - this.loadingThirdpartyPlugin = true - try { - await this.pluginServices.addPluginViaManifestUrl(manifestUrl) - this.loadingThirdpartyPlugin = false - this.matSnackbar.open(`Adding plugin successful`, 'Dismiss', { - duration: 5000 - }) - } catch (e) { - this.loadingThirdpartyPlugin = false - this.matSnackbar.open(`Error adding plugin: ${e.toString()}`, 'Dismiss', { - duration: 5000 - }) - } + private thirdpartyPlugin$: Subject<{name: 'Added Plugin', iframeUrl: string}> = new Subject() + + availablePlugins$: Observable<{ + name: string + iframeUrl: string + }[]> = combineLatest([ + this.svc.pluginManifests$, + this.thirdpartyPlugin$.pipe( + scan((acc, curr) => acc.concat(curr), []), + startWith([]) + ), + ]).pipe( + map(([builtIn, thirdParty]) => [...builtIn, ...thirdParty]) + ) + + public addThirdPartyPlugin(iframeUrl: string) { + this.thirdpartyPlugin$.next({ + name: 'Added Plugin', + iframeUrl + }) } } diff --git a/src/plugin/pluginBanner/pluginBanner.template.html b/src/plugin/pluginBanner/pluginBanner.template.html index 3e452612ebef6347181b825c02c22c5ba2ce8c74..4f9ef3ae2332b37d845eddc838c5519ce787dd2d 100644 --- a/src/plugin/pluginBanner/pluginBanner.template.html +++ b/src/plugin/pluginBanner/pluginBanner.template.html @@ -1,18 +1,9 @@ <mat-action-list> <button mat-menu-item - *ngFor="let plugin of pluginServices.fetchedPluginManifests" - [matTooltip]="plugin.displayName ? plugin.displayName : plugin.name" - (click)="clickPlugin(plugin)"> - <span mat-icon-button - aria-label="About this plugin" - class="mat-icon d-inline-flex align-items-center justify-content-center fa-stack" - (click)="showPluginInfo(plugin)" - iav-stop="click mousedown mouseup"> - <i class="fas fa-cube fa-stack-1x"></i> - <i class="fas fa-info-circle fa-stack-1x sub"></i> - </span> + *ngFor="let plugin of availablePlugins$ | async" + (click)="launchPlugin(plugin)"> <span> - {{ plugin.displayName ? plugin.displayName : plugin.name }} + {{ plugin.name }} </span> </button> @@ -33,9 +24,9 @@ <form> <mat-form-field class="d-block"> <mat-label> - manifest.json URL + iframe index.html URL </mat-label> - <input type="text" matInput placeholder="https://example.com/manifest.json" #urlInput> + <input type="text" matInput placeholder="https://example.com/index.html" #urlInput> </mat-form-field> </form> </mat-dialog-content> @@ -43,7 +34,6 @@ <mat-dialog-actions align="end"> <button (click)="addThirdPartyPlugin(urlInput.value)" mat-raised-button - [disabled]="loadingThirdpartyPlugin" color="primary"> Load </button> @@ -54,57 +44,3 @@ </mat-dialog-actions> </ng-template> - -<ng-template #pluginInfoTmpl let-manifest> - <h1 mat-dialog-title> - About {{ manifest.displayName || manifest.name }} - </h1> - - <div mat-dialog-content> - <mat-list> - <mat-list-item> - <span mat-list-icon class="d-inline-flex justify-content-center align-items-center"> - <i class="fas fa-info"></i> - </span> - <div mat-line> - Description - </div> - <div mat-line> - {{ manifest.description || manifest.desc || 'Not provided.' }} - </div> - </mat-list-item> - - <mat-list-item> - <span mat-list-icon class="d-inline-flex justify-content-center align-items-center"> - <i class="fas fa-users"></i> - </span> - <div mat-line> - Authors - </div> - <div mat-line> - {{ manifest.authors || 'Not provided' }} - </div> - </mat-list-item> - - <mat-list-item> - <span mat-list-icon class="d-inline-flex justify-content-center align-items-center"> - <i class="fas fa-globe-europe"></i> - </span> - <div mat-line> - Homepage - </div> - <div mat-line> - {{ manifest.homepage || 'Not provided' }} - </div> - </mat-list-item> - </mat-list> - - </div> - - <div mat-dialog-actions class="d-flex justify-content-center"> - <button mat-button mat-dialog-close> - close - </button> - </div> - -</ng-template> diff --git a/src/plugin/pluginCsp/pluginCsp.component.ts b/src/plugin/pluginCsp/pluginCsp.component.ts deleted file mode 100644 index 7ff3c8a1fcfcc7faa96237f9cbb9ad539104812c..0000000000000000000000000000000000000000 --- a/src/plugin/pluginCsp/pluginCsp.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Component } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { map } from "rxjs/operators"; -import { PluginServices } from "../atlasViewer.pluginService.service"; -import { selectorAllPluginsCspPermission } from "src/services/state/userConfigState.store"; - -@Component({ - selector: 'plugin-csp-controller', - templateUrl: './pluginCsp.template.html', - styleUrls: [ - './pluginCsp.style.css' - ] -}) - -export class PluginCspCtrlCmp{ - - public pluginCsp$ = this.store$.pipe( - select(selectorAllPluginsCspPermission), - map(pluginCsp => Object.keys(pluginCsp).map(key => ({ pluginKey: key, pluginCsp: pluginCsp[key] }))), - ) - - constructor( - private store$: Store<any>, - private pluginService: PluginServices, - ){ - - } - - revoke(pluginKey: string){ - this.pluginService.revokePluginPermission(pluginKey) - } -} \ No newline at end of file diff --git a/src/plugin/pluginCsp/pluginCsp.template.html b/src/plugin/pluginCsp/pluginCsp.template.html deleted file mode 100644 index 16159fa0bf2b0525eed22819c9d06d4315fec6e4..0000000000000000000000000000000000000000 --- a/src/plugin/pluginCsp/pluginCsp.template.html +++ /dev/null @@ -1,52 +0,0 @@ - -<ng-container *ngIf="pluginCsp$ | async as pluginsCsp; else fallbackTmpl"> - - <ng-template #pluginsCspContainerTmpl> - <ng-container *ngTemplateOutlet="pluginCpTmpl; context: { pluginsCsp: pluginsCsp }"> - </ng-container> - </ng-template> - - <ng-container *ngIf="pluginsCsp.length === 0; else pluginsCspContainerTmpl"> - <ng-container *ngTemplateOutlet="fallbackTmpl"> - </ng-container> - </ng-container> -</ng-container> - -<ng-template #fallbackTmpl> - You have not granted permission to any plugins. -</ng-template> - -<ng-template #pluginCpTmpl let-pluginsCsp="pluginsCsp"> - <p> - You have granted permission to the following plugins - </p> - - <mat-accordion> - <mat-expansion-panel *ngFor="let pluginCsp of pluginCsp$ | async"> - <mat-expansion-panel-header> - <mat-panel-title> - {{ pluginCsp['pluginKey'] }} - </mat-panel-title> - </mat-expansion-panel-header> - - <button mat-raised-button - color="warn" - (click)="revoke(pluginCsp['pluginKey'])"> - Revoke - </button> - - <mat-list> - <ng-container *ngFor="let csp of pluginCsp['pluginCsp'] | keyvalue"> - <span mat-subheader> - {{ csp['key'] }} - </span> - <mat-list-item *ngFor="let item of csp['value']"> - {{ item }} - </mat-list-item> - </ng-container> - </mat-list> - - </mat-expansion-panel> - </mat-accordion> - -</ng-template> diff --git a/src/plugin/pluginFactory.directive.spec.ts b/src/plugin/pluginFactory.directive.spec.ts deleted file mode 100644 index 29d723d972543473d647354c82dd829dfaa4dda3..0000000000000000000000000000000000000000 --- a/src/plugin/pluginFactory.directive.spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { async, TestBed } from "@angular/core/testing" -import { PluginFactoryDirective, REGISTER_PLUGIN_FACTORY_DIRECTIVE } from "./pluginFactory.directive" -import { Component, ViewChild } from "@angular/core" -import { APPEND_SCRIPT_TOKEN, REMOVE_SCRIPT_TOKEN } from "src/util/constants" -import { By } from "@angular/platform-browser" - -@Component({ - template: '<div></div>' -}) -class TestCmp{ - - @ViewChild(PluginFactoryDirective) pfd: PluginFactoryDirective -} - -const dummyObj1 = {} -const dummyObj2 = {} -const appendSrcSpy = jasmine.createSpy('appendSrc').and.returnValues( - Promise.resolve(dummyObj1), - Promise.resolve(dummyObj2) -) -const removeSrcSpy = jasmine.createSpy('removeScript') -const registerSpy = jasmine.createSpy('registerSpy') - -describe(`> pluginFactory.directive.ts`, () => { - describe(`> PluginFactoryDirective`, () => { - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ - PluginFactoryDirective, - TestCmp - ], - providers: [ - { - provide: APPEND_SCRIPT_TOKEN, - useValue: appendSrcSpy - }, - { - provide: REMOVE_SCRIPT_TOKEN, - useValue: removeSrcSpy - }, - { - provide: REGISTER_PLUGIN_FACTORY_DIRECTIVE, - useValue: registerSpy - } - ] - }).overrideComponent(TestCmp, { - set: { - template: `<div pluginFactoryDirective></div>` - } - }).compileComponents() - })) - - afterEach(() => { - appendSrcSpy.calls.reset() - removeSrcSpy.calls.reset() - registerSpy.calls.reset() - }) - - it('> creates directive', () => { - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - - const queriedDirective = fixture.debugElement.query( By.directive(PluginFactoryDirective) ) - expect(queriedDirective).toBeTruthy() - }) - - it('> register spy is called', () => { - - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - expect(registerSpy).toHaveBeenCalledWith(fixture.componentInstance.pfd) - }) - - describe('> loading external libraries', () => { - it('> load once, call append script', async () => { - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - const pfd = fixture.componentInstance.pfd - await pfd.loadExternalLibraries(['vue@2.5.16']) - expect(appendSrcSpy).toHaveBeenCalledWith('https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js') - expect(appendSrcSpy).toHaveBeenCalledTimes(1) - }) - - it('> load twice, called append script once', async () => { - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - const pfd = fixture.componentInstance.pfd - await pfd.loadExternalLibraries(['vue@2.5.16']) - await pfd.loadExternalLibraries(['vue@2.5.16']) - expect(appendSrcSpy).toHaveBeenCalledWith('https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js') - expect(appendSrcSpy).toHaveBeenCalledTimes(1) - }) - - it('> load unload, call remove script once', async () => { - - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - const pfd = fixture.componentInstance.pfd - await pfd.loadExternalLibraries(['vue@2.5.16']) - pfd.unloadExternalLibraries(['vue@2.5.16']) - expect(removeSrcSpy).toHaveBeenCalledTimes(1) - }) - - it('> load twice, unload, does not call remove', async () => { - - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - const pfd = fixture.componentInstance.pfd - await pfd.loadExternalLibraries(['vue@2.5.16']) - await pfd.loadExternalLibraries(['vue@2.5.16']) - pfd.unloadExternalLibraries(['vue@2.5.16']) - expect(removeSrcSpy).not.toHaveBeenCalled() - }) - - it('> load, unload, load, call append script twice', async () => { - - const fixture = TestBed.createComponent(TestCmp) - fixture.detectChanges() - const pfd = fixture.componentInstance.pfd - await pfd.loadExternalLibraries(['vue@2.5.16']) - pfd.unloadExternalLibraries(['vue@2.5.16']) - - appendSrcSpy.calls.reset() - expect(appendSrcSpy).not.toHaveBeenCalled() - - await pfd.loadExternalLibraries(['vue@2.5.16']) - pfd.unloadExternalLibraries(['vue@2.5.16']) - expect(appendSrcSpy).toHaveBeenCalledTimes(1) - }) - }) - }) -}) \ No newline at end of file diff --git a/src/plugin/pluginFactory.directive.ts b/src/plugin/pluginFactory.directive.ts deleted file mode 100644 index fcd47654120dcf3754471b1fc6e7eac2342cb57e..0000000000000000000000000000000000000000 --- a/src/plugin/pluginFactory.directive.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Directive, ViewContainerRef, Inject, Optional } from "@angular/core"; -import { APPEND_SCRIPT_TOKEN, REMOVE_SCRIPT_TOKEN } from "src/util/constants"; - -export const SUPPORT_LIBRARY_MAP: Map<string, Map<string, string>> = new Map([ - ['jquery', new Map<string, string>([ - ['3', 'https://code.jquery.com/jquery-3.3.1.min.js'], - ['2', 'https://code.jquery.com/jquery-2.2.4.min.js'] - ])], - ['webcomponentsLite', new Map([ - ['1.1.0', 'https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.1.0/webcomponents-lite.js'] - ])], - ['react', new Map([ - ['16', 'https://unpkg.com/react@16/umd/react.development.js'] - ])], - ['reactdom', new Map([ - ['16', 'https://unpkg.com/react-dom@16/umd/react-dom.development.js'] - ])], - ['vue', new Map([ - ['2.5.16', 'https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js'] - ])], - ['preact', new Map([ - ['8.4.2', 'https://cdn.jsdelivr.net/npm/preact@8.4.2/dist/preact.min.js'] - ])], - ['d3', new Map([ - ['5.7.0', 'https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js'], - ['6.2.0', 'https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js'] - ])], - ['mathjax', new Map([ - ['3.1.2', 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.1.2/es5/tex-svg.js'] - ])] -]) - -export const parseLibrary = (libVer: string) => { - const re = /^([a-zA-Z0-9]+)@([0-9.]+)$/.exec(libVer) - if (!re) throw new Error(`${libVer} cannot be parsed properly`) - const lib = re[1] - const ver = re[2] - const libMap = SUPPORT_LIBRARY_MAP.get(lib) - if (!libMap) throw new Error(`${lib} not supported. Only supported libraries are ${Array.from(SUPPORT_LIBRARY_MAP.keys())}`) - const src = libMap.get(ver) - if (!src) throw new Error(`${lib} version ${ver} not supported. Only supports ${Array.from(libMap.keys())}`) - return src -} - -export const REGISTER_PLUGIN_FACTORY_DIRECTIVE = `REGISTER_PLUGIN_FACTORY_DIRECTIVE` - -@Directive({ - selector: '[pluginFactoryDirective]', -}) - -export class PluginFactoryDirective { - constructor( - public viewContainerRef: ViewContainerRef, - @Optional() @Inject(REGISTER_PLUGIN_FACTORY_DIRECTIVE) registerPluginFactoryDirective: (directive: PluginFactoryDirective) => void, - @Inject(APPEND_SCRIPT_TOKEN) private appendScript: (src: string) => Promise<HTMLScriptElement>, - @Inject(REMOVE_SCRIPT_TOKEN) private removeScript: (srcEl: HTMLScriptElement) => void, - ) { - if (registerPluginFactoryDirective) { - registerPluginFactoryDirective(this) - } - } - - private loadedLibraries: Map<string, {counter: number, srcEl: HTMLScriptElement|null}> = new Map() - - async loadExternalLibraries(libraries: string[]) { - const libsToBeLoaded = libraries.map(libName => { - return { - libName, - libSrc: parseLibrary(libName), - } - }) - - for (const libToBeLoaded of libsToBeLoaded) { - - const { libSrc, libName } = libToBeLoaded - - // if browser natively support custom element, do not append polyfill - if ('customElements' in window && /^webcomponentsLite@/.test(libName)) continue - - let srcEl - const { counter, srcEl: srcElOld } = this.loadedLibraries.get(libName) || { counter: 0 } - if (counter === 0) { - - // slight performance penalty not loading external libraries in parallel, but this should be an edge case any way - srcEl = await this.appendScript(libSrc) - } - this.loadedLibraries.set(libName, { counter: counter + 1, srcEl: srcEl || srcElOld }) - } - } - - unloadExternalLibraries(libraries: string[]) { - for (const lib of libraries) { - const { counter, srcEl } = this.loadedLibraries.get(lib) || { counter: 0 } - if (counter > 1) { - this.loadedLibraries.set(lib, { counter: counter - 1, srcEl }) - } else { - this.loadedLibraries.set(lib, { counter: 0, srcEl: null }) - this.removeScript(srcEl) - } - } - } -} diff --git a/src/plugin/pluginPortal/pluginPortal.component.ts b/src/plugin/pluginPortal/pluginPortal.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..58bd58993f3c2423ed278fee3c64b330b78b4af2 --- /dev/null +++ b/src/plugin/pluginPortal/pluginPortal.component.ts @@ -0,0 +1,149 @@ +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Inject, OnDestroy, Optional, ViewChild, ViewContainerRef } from "@angular/core"; +import { combineLatest, fromEvent, interval, Subscription } from "rxjs"; +import { map, scan, share, startWith, take, filter } from "rxjs/operators"; +import { BoothVisitor, JRPCRequest, JRPCSuccessResp, ListenerChannel } from "src/api/jsonrpc"; +import { ApiBoothEvents, ApiService, BroadCastingApiEvents, HeartbeatEvents, namespace } from "src/api/service"; +import { getUuid } from "src/util/fn"; +import { WIDGET_PORTAL_TOKEN } from "src/widget/constants"; +import { getPluginSrc, SET_PLUGIN_NAME } from "../const"; + +@Component({ + selector: 'sxplr-plugin-portal', + template: ` + <iframe [src]="src | iframeSrc" [sandbox]="sandbox" #iframe> + </iframe> + `, + styles: [ + `:host { width: 100%; height: 100%; display: block; } iframe { width: 100%; height: 100%; border: none; }` + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) + +export class PluginPortal implements AfterViewInit, OnDestroy, ListenerChannel{ + + sandbox = [ + "allow-downloads", + "allow-popups", + "allow-popups-to-escape-sandbox", + "allow-scripts", + ].join(" ") + + @ViewChild('iframe', { read: ElementRef }) + iframeElRef: ElementRef + src: string + srcName: string + private origin: string + + private onDestroyCb: (() => void)[] = [] + private sub: Subscription[] = [] + private handshakeSub: Subscription[] = [] + + private boothVisitor: BoothVisitor<ApiBoothEvents> + private childWindow: Window + + constructor( + private apiService: ApiService, + public vcr: ViewContainerRef, + @Optional() @Inject(SET_PLUGIN_NAME) private setPluginName: (inst: unknown, pluginName: string) => void, + @Optional() @Inject(WIDGET_PORTAL_TOKEN) portalData: Record<string, string> + ){ + if (portalData){ + this.src = getPluginSrc(portalData) + const url = new URL(this.src) + this.origin = url.origin + } + } + + ngAfterViewInit(): void { + if (this.iframeElRef) { + const iframeWindow = (this.iframeElRef.nativeElement as HTMLIFrameElement).contentWindow + const handShake$ = interval(1000).pipe( + map(() => getUuid()), + take(10), + share() + ) + this.handshakeSub.push( + /** + * handshake + */ + handShake$.pipe( + // try for 10 seconds. If nothing loads within 10 minutes, assuming dead. + ).subscribe(id => { + const handshakeMsg: JRPCRequest<string, null> = { + jsonrpc: '2.0', + id, + method: `${namespace}.init`, + } + iframeWindow.postMessage(handshakeMsg, this.origin) + }), + + combineLatest([ + handShake$.pipe( + scan((acc, curr) => [...acc, curr], []) + ), + fromEvent<MessageEvent>(window, 'message').pipe( + startWith(null as MessageEvent), + ) + ]).subscribe(([ids, event]) => { + const { id, jsonrpc } = event?.data || {} + if (jsonrpc === "2.0" && ids.includes(id)) { + const data = event.data as JRPCSuccessResp<HeartbeatEvents['init']['response']> + + this.srcName = data.result.name || 'Untitled Pluging' + this.setPluginName(this, this.srcName) + + while (this.handshakeSub.length > 0) this.handshakeSub.pop().unsubscribe() + + /** + * hook up to the listener for the plugin + */ + this.childWindow = iframeWindow + this.apiService.broadcastCh.addListener(this) + this.boothVisitor = this.apiService.booth.handshake() + } + }) + ) + + /** + * listening to plugin requests + * only start after boothVisitor is defined + */ + const sub = fromEvent<MessageEvent>(window, 'message').pipe( + startWith(null as MessageEvent), + filter(msg => !!this.boothVisitor && msg.data.jsonrpc === "2.0" && !!msg.data.method && msg.origin === this.origin) + ).subscribe(async msg => { + try { + if (msg.data.method === `${namespace}.exit`) { + sub.unsubscribe() + } + const result = await this.boothVisitor.request(msg.data) + if (!!result) { + this.childWindow.postMessage(result, this.origin) + } + } catch (e) { + this.childWindow.postMessage({ + id: msg.data.id, + error: { + code: -32603, + message: e.toString() + } + }, this.origin) + } + }) + } + } + notify(payload: JRPCRequest<keyof BroadCastingApiEvents, BroadCastingApiEvents[keyof BroadCastingApiEvents]>) { + if (this.childWindow) { + this.childWindow.postMessage(payload, this.origin) + } + } + registerLeaveCb(cb: () => void) { + this.onDestroyCb.push(() => cb()) + } + + ngOnDestroy(): void { + while (this.handshakeSub.length > 0) this.handshakeSub.pop().unsubscribe() + while (this.sub.length > 0) this.sub.pop().unsubscribe() + while (this.onDestroyCb.length > 0) this.onDestroyCb.pop()() + } +} diff --git a/src/plugin/pluginUnit/pluginUnit.component.ts b/src/plugin/pluginUnit/pluginUnit.component.ts deleted file mode 100644 index 902701b16c29791974171a16841beb85d7ee3fee..0000000000000000000000000000000000000000 --- a/src/plugin/pluginUnit/pluginUnit.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, ElementRef, HostBinding } from "@angular/core"; - -@Component({ - templateUrl : `./pluginUnit.template.html`, -}) - -export class PluginUnit { - - @HostBinding('attr.pluginContainer') - public pluginContainer = true - - constructor(public elementRef: ElementRef) { - - } -} diff --git a/src/plugin/request.md b/src/plugin/request.md new file mode 100644 index 0000000000000000000000000000000000000000..487c1344f3d785978a99eb61f1ec94ca6b31904e --- /dev/null +++ b/src/plugin/request.md @@ -0,0 +1,264 @@ +# Request API + +Request messages are sent when the plugin requests siibra-explorer to do something on its behalf. + +Be it request the user to select a region, a point, navigate to a specific location etc. + +> :warning: Please note that `beforeunload` window event does not fire on iframe windows. Plugins should do whatever cleanup it needs, then send the message `sxplr.exit`. + +```javascript + +let parentWindow +window.addEventListener('message', ev => { + const { source, data, origin } = msg + const { id, method, params, result, error } = data + + if (method === "sxplr.init") { + parentWindow = source + } +}) + +window.addEventListener('pagehide', () => { + + // do cleanup + // n.b. since iframe unload usually do not trigger DOM events + // one will need to manually trigger destroying any apps manually + + parentWindow.postMessage({ + jsonrpc: '2.0', + method: `sxplr.exit`, + params: { + requests: [] // any remaining requests to be carried out + } + }) +}) +``` + +<!-- the API reference below are auto generated by generateTypes.js --> +<!-- do not edit, as the edit will be overwritten by the auto generation --> + +## API +### `sxplr.getAllAtlases` + +- request + + ```ts + null + ``` + +- response + + ```ts + SapiAtlasModel[] + ``` + + +### `sxplr.getSupportedTemplates` + +- request + + ```ts + null + ``` + +- response + + ```ts + SapiSpaceModel[] + ``` + + +### `sxplr.getSupportedParcellations` + +- request + + ```ts + null + ``` + +- response + + ```ts + SapiParcellationModel[] + ``` + + +### `sxplr.selectAtlas` + +- request + + ```ts + {"@id": string} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.selectParcellation` + +- request + + ```ts + {"@id": string} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.selectTemplate` + +- request + + ```ts + {"@id": string} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.navigateTo` + +- request + + ```ts + MainState['[state.atlasSelection]']['navigation'] & {"animate": boolean} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.getUserToSelectARoi` + +- request + + ```ts + {"type": 'region' | 'point', "message": string} + ``` + +- response + + ```ts + SapiRegionModel | OpenMINDSCoordinatePoint + ``` + + +### `sxplr.addAnnotations` + +- request + + ```ts + {"annotations": SxplrCoordinatePointExtension[]} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.rmAnnotations` + +- request + + ```ts + {"annotations": AtId[]} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.loadLayers` + +- request + + ```ts + {"layers": AddableLayer[]} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.updateLayers` + +- request + + ```ts + {"layers": AddableLayer[]} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.removeLayers` + +- request + + ```ts + {"layers": {"id": string}} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.exit` + +- request + + ```ts + {"requests": JRPCRequest[]} + ``` + +- response + + ```ts + 'OK' + ``` + + +### `sxplr.cancelRequest` + +- request + + ```ts + {"id": string} + ``` + +- response + + ```ts + 'OK' + ``` + diff --git a/src/plugin/service.ts b/src/plugin/service.ts new file mode 100644 index 0000000000000000000000000000000000000000..19f183189fac54e16b9b5a33be58de632276327a --- /dev/null +++ b/src/plugin/service.ts @@ -0,0 +1,68 @@ +import { HttpClient } from "@angular/common/http"; +import { Injectable, Injector, NgZone } from "@angular/core"; +import { WIDGET_PORTAL_TOKEN } from "src/widget/constants"; +import { WidgetService } from "src/widget/service"; +import { WidgetPortal } from "src/widget/widgetPortal/widgetPortal.component"; +import { setPluginSrc, SET_PLUGIN_NAME } from "./const"; +import { PluginPortal } from "./pluginPortal/pluginPortal.component"; +import { environment } from "src/environments/environment" + +@Injectable({ + providedIn: 'root' +}) +export class PluginService { + loadedPlugins: string[] = [] + srcWidgetMap = new Map<string, WidgetPortal<PluginPortal>>() + + constructor( + private wSvc: WidgetService, + private injector: Injector, + private zone: NgZone, + private http: HttpClient + ){} + + pluginManifests$ = this.http.get<{ + 'siibra-explorer': true + name: string + iframeUrl: string + }[]>(`${environment.BACKEND_URL || ''}plugins/manifests`) + + async launchPlugin(htmlSrc: string){ + if (this.loadedPlugins.includes(htmlSrc)) return + const injector = Injector.create({ + providers: [{ + provide: WIDGET_PORTAL_TOKEN, + useValue: setPluginSrc(htmlSrc, {}) + }, { + provide: SET_PLUGIN_NAME, + useValue: (inst: PluginPortal, pluginName: string) => this.setPluginName(inst, pluginName) + }], + parent: this.injector + }) + const wdg = this.wSvc.addNewWidget(PluginPortal, injector) + this.srcWidgetMap.set(htmlSrc, wdg) + } + + setPluginName(plg: PluginPortal, name: string) { + + if (!this.srcWidgetMap.has(plg.src)) { + console.warn(`cannot find plg.src ${plg.src}`) + return + } + const wdg = this.srcWidgetMap.get(plg.src) + this.zone.run(() => wdg.name = name) + } + + rmPlugin(plg: PluginPortal){ + this.loadedPlugins = this.loadedPlugins.filter(plgSrc => plgSrc !== plg.src) + + if (!this.srcWidgetMap.has(plg.src)) { + console.warn(`cannot find plg.src ${plg.src}`) + return + } + const wdg = this.srcWidgetMap.get(plg.src) + this.srcWidgetMap.delete(plg.src) + + this.wSvc.rmWidget(wdg) + } +} diff --git a/src/plugin/tsUtil.js b/src/plugin/tsUtil.js new file mode 100644 index 0000000000000000000000000000000000000000..ea0040ad800c55dd5caa06c2ddf79a91b82b99fe --- /dev/null +++ b/src/plugin/tsUtil.js @@ -0,0 +1,130 @@ +const ts = require('typescript') + + +function processIndexAccessor(mem) { + if (ts.SyntaxKind[mem.kind] !== "IndexedAccessType") { + throw new Error(`Index accessor needs to have index accessor type as mem.kind`) + } + return `${getTypeText(mem.objectType)}[${getTypeText(mem.indexType)}]` +} + +function getTypeText(node){ + switch(ts.SyntaxKind[node.kind]) { + case "TypeReference": { + return node.typeName.text + } + case "ArrayType": { + if (!node.elementType.typeName) return getTypeText(node.elementType) + return `${node.elementType.typeName.text}[]` + } + case "TypeLiteral": { + let returnVal = '{' + returnVal += node.members.map(mem => `"${mem.name.text}": ${getTypeText(mem.type)}`).join(', ') + returnVal += "}" + return returnVal + } + case "LiteralType": { + if (ts.SyntaxKind[node.literal.kind] === "NullKeyword"){ + return `null` + } + if (ts.SyntaxKind[node.literal.kind] === "StringLiteral"){ + return `'${node.literal.text}'` + } + throw new Error(`LiteralType not caught`) + } + case "BooleanKeyword": { + return `boolean` + } + case "StringKeyword": { + return `string` + } + case "IntersectionType": { + return node.types.map(getTypeText).join(' & ') + } + case "UnionType": { + return node.types.map(getTypeText).join(' | ') + } + case 'PropertySignature': { + return processPropertySignature(node) + } + case 'IndexedAccessType': { + return processIndexAccessor(node) + } + default: { + debugger + throw new Error(`No parser for type ${ts.SyntaxKind[node.kind]}`) + } + } +} + +function processPropertySignature(node) { + const output = {} + debugger +} + +function processNodeMember(mem, typeAliasDeclarationMap = new Map()) { + if (ts.SyntaxKind[mem.kind] === "IndexedAccessType") { + return processIndexAccessor(mem) + } + if (ts.SyntaxKind[mem.kind] === "TypeReference") { + return mem.typeName.text + } + if (ts.SyntaxKind[mem.kind] !== "PropertySignature") { + throw new Error(`mem.kind should be of PropertySignature, but is instead ${ts.SyntaxKind[mem.kind]}`) + } + const typeText = getTypeText(mem.type) + if (typeAliasDeclarationMap.has(typeText)) { + return getTypeText(typeAliasDeclarationMap.get(typeText).type) + } + return typeText +} + +function processTypeAliasDeclaration(node) { + const output = {} + const kind = ts.SyntaxKind[node.kind] + if (kind !== 'TypeAliasDeclaration') throw new Error(`processTypeAliasDeclaration should be of TypeAliasDeclaration`) + for (const mem of node.type.members) { + output[mem.name.text] = processNodeMember(mem) + } + return output +} + +function processRequestTypeAlias(node, typeAliasDeclarationMap = new Map()) { + const kind = ts.SyntaxKind[node.kind] + if (kind !== 'TypeAliasDeclaration') throw new Error(`processTypeAliasDeclaration should be of TypeAliasDeclaration`) + if (!node.type.members.every(mem => ts.SyntaxKind[mem.type.kind] === "TypeLiteral")) { + throw new Error(`for request type alias, expected every type.members to be of type TypeLiteral`) + } + + const output = {} + for (const mem of node.type.members) { + + const requestNode = mem.type.members.find(typeMem => typeMem.name.text === "request") + const responseNode = mem.type.members.find(typeMem => typeMem.name.text === "response") + if (!requestNode || !responseNode) { + let errorText = `for request type alias, every member must have both response and request defined, but ${node.name.text}.${mem.name.text} does not have ` + if (!requestNode) { + errorText += " request " + } + if (!responseNode) { + errorText += " response " + } + errorText += "defined." + throw new Error(errorText) + } + if (!requestNode) { + throwFlag = true + errorText += ` request ` + } + output[mem.name.text] = { + request: processNodeMember(requestNode, typeAliasDeclarationMap), + response: processNodeMember(responseNode, typeAliasDeclarationMap) + } + } + return output +} + +module.exports = { + processTypeAliasDeclaration, + processRequestTypeAlias, +} \ No newline at end of file diff --git a/src/plugin/types.ts b/src/plugin/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..f51aa57d9deb5227fbc76075177607b3e38bcd70 --- /dev/null +++ b/src/plugin/types.ts @@ -0,0 +1,4 @@ +export type PluginManifest = { + name: string + iframeUrl: string +} diff --git a/src/plugin_examples/README.md b/src/plugin_examples/README.md deleted file mode 100644 index 7f967539c0997992f1dae6e391a72e084d5a33a5..0000000000000000000000000000000000000000 --- a/src/plugin_examples/README.md +++ /dev/null @@ -1,118 +0,0 @@ -# Plugin README - -A plugin needs to contain three files. -- Manifest JSON -- template HTML -- script JS - - -These files need to be served by GET requests over HTTP with appropriate CORS header. - ---- - -## Manifest JSON - -The manifest JSON file describes the metadata associated with the plugin. - -```json -{ - "name":"fzj.xg.helloWorld", - "displayName": "Hello World - my first plugin", - "templateURL":"http://LINK-TO-YOUR-PLUGIN-TEMPLATE/template.html", - "scriptURL":"http://LINK-TO-YOUR-PLUGIN-SCRIPT/script.js", - "initState":{ - "key1": "value1", - "key2" : { - "nestedKey1" : "nestedValue1" - } - }, - "initStateUrl": "http://LINK-TO-PLUGIN-STATE", - "persistency": false, - - "description": "Human readable description of the plugin.", - "desc": "Same as description. If both present, description takes more priority.", - "homepage": "https://HOMEPAGE-URL-TO-YOUR-PLUGIN/doc.html", - "authors": "Author <author@example.com>, Author2 <author2@example.org>" -} -``` -*NB* -- Plugin name must be unique globally. To prevent plugin name clashing, please adhere to the convention of naming your package **AFFILIATION.AUTHORNAME.PACKAGENAME\[.VERSION\]**. -- the `initState` object and `initStateUrl` will be available prior to the evaluation of `script.js`, and will populate the objects `interactiveViewer.pluginControl[MANIFEST.name].initState` and `interactiveViewer.pluginControl[MANIFEST.name].initStateUrl` respectively. - ---- - -## Template HTML - -The template HTML file describes the HTML view that will be rendered in the widget. - - -```html -<form> - <div class = "input-group"> - <span class = "input-group-addon">Area 1</span> - <input type = "text" id = "fzj.xg.helloWorld.area1" name = "fzj.xg.helloWorld.area1" class = "form-control" placeholder="Select a region" value = ""> - </div> - - <div class = "input-group"> - <span class = "input-group-addon">Area 2</span> - <input type = "text" id = "fzj.xg.helloWorld.area2" name = "fzj.xg.helloWorld.area2" class = "form-control" placeholder="Select a region" value = ""> - </div> - - <hr class = "col-md-10"> - - <div class = "col-md-12"> - Select genes of interest: - </div> - <div class = "input-group"> - <input type = "text" id = "fzj.xg.helloWorld.genes" name = "fzj.xg.helloWorld.genes" class = "form-control" placeholder = "Genes of interest ..."> - <span class = "input-group-btn"> - <button id = "fzj.xg.helloWorld.addgenes" name = "fzj.xg.helloWorld.addgenes" class = "btn btn-default" type = "button">Add</button> - </span> - </div> - - <hr class = "col-md-10"> - - <button id = "fzj.xg.helloWorld.submit" name = "fzj.xg.helloWorld.submit" type = "button" class = "btn btn-default btn-block">Submit</button> - - <hr class = "col-md-10"> - - <div class = "col-md-12" id = "fzj.xg.helloWorld.result"> - - </div> -</form> -``` - -*NB* -- *bootstrap 3.3.6* css is already included for templating. -- keep in mind of the widget width restriction (400px) when crafting the template -- whilst there are no vertical limits on the widget, contents can be rendered outside the viewport. Consider setting the *max-height* attribute. -- your template and script will interact with each other likely via *element id*. As a result, it is highly recommended that unique id's are used. Please adhere to the convention: **AFFILIATION.AUTHOR.PACKAGENAME.ELEMENTID** - ---- - -## Script JS - -The script will always be appended **after** the rendering of the template. - -```javascript -(()=>{ - /* your code here */ - - if(interactiveViewer.pluginControl['fzj.xg.helloWorld'].initState){ - /* init plugin with initState */ - } - - const submitButton = document.getElemenById('fzj.xg.helloWorld.submit') - submitButton.addEventListener('click',(ev)=>{ - console.log('submit button was clicked') - }) -})() -``` -*NB* -- JS is loaded and executed **before** the attachment of DOM (template). This is to allow webcomponents have a chance to be loaded. If your script needs the DOM to be attached, use a `setTimeout` callback to delay script execution. -- ensure the script is scoped locally, instead of poisoning the global scope -- for every observable subscription, call *unsubscribe()* in the *onShutdown* callback -- some frameworks such as *jquery2*, *jquery3*, *react/reactdom* and *webcomponents* can be loaded via *interactiveViewer.pluinControl.loadExternalLibraries([LIBRARY_NAME_1, LIBRARY_NAME_2])*. if the libraries are loaded, remember to hook *interactiveViewer.pluginControl.unloadExternalLibraries([LIBRARY_NAME_1,LIBRARY_NAME_2])* in the *onShutdown* callback -- when/if using webcomponents, please be aware that the `connectedCallback()` and `disconnectedCallback()` will be called everytime user toggle between *floating* and *docked* modes. -- when user navigate to a new template all existing widgets will be destroyed, unless the `persistency` is set to `true` in `manifest.json`. -- for a list of APIs, see [plugin_api.md](plugin_api.md) diff --git a/src/plugin_examples/migrationGuide.md b/src/plugin_examples/migrationGuide.md deleted file mode 100644 index fcd5e040b9333b262dbbac60b6d3237859fe7f1c..0000000000000000000000000000000000000000 --- a/src/plugin_examples/migrationGuide.md +++ /dev/null @@ -1,51 +0,0 @@ -Plugin Migration Guide (v0.1.0 => v0.2.0) -====== -Plugin APIs have changed drastically from v0.1.0 to v0.2.0. Here is a list of plugin API from v0.1.0, and how it has changed moving to v0.2.0. - -**n.b.** `webcomponents-lite.js` is no longer included by default. You will need to request it explicitly with `window.interactiveViewer.pluginControl.loadExternalLibraries()` and unload it once you are done. - ---- - -- ~~*window.nehubaUI*~~ removed - - ~~*metadata*~~ => **window.interactiveViewer.metadata** - - ~~*selectedTemplate* : nullable Object~~ removed. use **window.interactiveViewer.metadata.selectedTemplateBSubject** instead - - ~~*availableTemplates* : Array of TemplateDescriptors (empty array if no templates are available)~~ => **window.interactiveViewer.metadata.loadedTemplates** - - ~~*selectedParcellation* : nullable Object~~ removed. use **window.interactiveViewer.metadata.selectedParcellationBSubject** instead - - ~~*selectedRegions* : Array of Object (empty array if no regions are selected)~~ removed. use **window.interactiveViewer.metadata.selectedRegionsBSubject** instead - -- ~~window.pluginControl['YOURPLUGINNAME'] *nb: may be undefined if yourpluginname is incorrect*~~ => **window.interactiveViewer.pluginControl[YOURPLUGINNAME]** - - blink(sec?:number) : Function that causes the floating widget to blink, attempt to grab user attention - - ~~pushMessage(message:string) : Function that pushes a message that are displayed as a popover if the widget is minimised. No effect if the widget is not miniminised.~~ removed - - shutdown() : Function that causes the widget to shutdown dynamically. (triggers onShutdown callback) - - onShutdown(callback) : Attaches a callback function, which is called when the plugin is shutdown. - -- ~~*window.viewerHandle*~~ => **window.interactiveViewer.viewerHandle** - - ~~*loadTemplate(TemplateDescriptor)* : Function that loads a new template~~ removed. use **window.interactiveViewer.metadata.selectedTemplateBSubject** instead - - ~~*onViewerInit(callback)* : Functional that allows a callback function to be called just before a nehuba viewer is initialised~~ removed - - ~~*afterViewerInit(callback)* : Function that allows a callback function to be called just after a nehuba viewer is initialised~~ removed - - ~~*onViewerDestroy(callback)* : Function that allows a callback function be called just before a nehuba viewer is destroyed~~ removed - - ~~*onParcellationLoading(callback)* : Function that allows a callback function to be called just before a parcellation is selected~~ removed - - ~~*afterParcellationLoading(callback)* : Function that allows a callback function to be called just after a parcellation is selected~~ removed - - *setNavigationLoc(loc,realSpace?)* : Function that teleports to loc : number[3]. Optional argument to determine if the loc is in realspace (default) or voxelspace. - - ~~*setNavigationOrientation(ori)* : Function that teleports to ori : number[4]. (Does not work currently)~~ => **setNavigationOri(ori)** (still non-functional) - - *moveToNavigationLoc(loc,realSpace?)* : same as *setNavigationLoc(loc,realSpace?)*, except moves to target location over 500ms. - - *showSegment(id)* : Function that selectes a segment in the viewer and UI. - - *hideSegment(id)* : Function that deselects a segment in the viewer and UI. - - *showAllSegments()* : Function that selects all segments. - - *hideAllSegments()* : Function that deselects all segments. - - *loadLayer(layerObject)* : Function that loads a custom neuroglancer compatible layer into the viewer (e.g. precomputed, NIFTI, etc). Does not influence UI. - - *mouseEvent* RxJs Observable. Read more at [rxjs doc](http://reactivex.io/rxjs/) - - *mouseEvent.filter(filterFn:({eventName : String, event: Event})=>boolean)* returns an Observable. Filters the event stream according to the filter function. - - *mouseEvent.map(mapFn:({eventName : String, event: Event})=>any)* returns an Observable. Map the event stream according to the map function. - - *mouseEvent.subscribe(callback:({eventName : String , event : Event})=>void)* returns an Subscriber instance. Call *Subscriber.unsubscribe()* when done to avoid memory leak. - - *mouseOverNehuba* RxJs Observable. Read more at [rxjs doc](http://reactivex.io/rxjs) - - *mouseOverNehuba.filter* && *mouseOvernehuba.map* see above - - *mouseOverNehuba.subscribe(callback:({nehubaOutput : any, foundRegion : any})=>void)* - -- ~~*window.uiHandle*~~ => **window.interactiveViewer.uiHandle** - - ~~*onTemplateSelection(callback)* : Function that allows a callback function to be called just after user clicks to navigate to a new template, before *selectedTemplate* is updated~~ removed. use **window.interactiveViewer.metadata.selectedTemplateBSubject** instead - - ~~*afterTemplateSelection(callback)* : Function that allows a callback function to be called after the template selection process is complete, and *selectedTemplate* is updated~~ removed - - ~~*onParcellationSelection(callback)* : Function that attach a callback function to user selecting a different parcellation~~ removed. use **window.interactiveViewer.metadata.selectedParcellationBSubject** instead. - - ~~*afterParcellationSelection(callback)* : Function that attach a callback function to be called after the parcellation selection process is complete and *selectedParcellation* is updated.~~ removed - - *modalControl* - - ~~*getModalHandler()* : Function returning a handler to change/show/hide/listen to a Modal.~~ removed \ No newline at end of file diff --git a/src/plugin_examples/plugin1/manifest.json b/src/plugin_examples/plugin1/manifest.json deleted file mode 100644 index 0813f787c22dc71f22c3d14bbd20de2716d27068..0000000000000000000000000000000000000000 --- a/src/plugin_examples/plugin1/manifest.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name":"fzj.xg.exmaple.0_0_1", - "displayName": "Example Plugin (v0.0.1)", - "templateURL": "http://HOSTNAME/test.html", - "scriptURL": "http://HOSTNAME/script.js", - "initState": { - "key1": "val1", - "key2": { - "key21": "val21" - } - }, - "initStateUrl": "http://HOSTNAME/state?id=007", - "persistency": false, - "description": "description of example plugin", - "desc": "desc of example plugin", - "homepage": "http://HOSTNAME/home.html", - "authors": "Xiaoyun Gui <x.gui@fz-juelich.de>" -} \ No newline at end of file diff --git a/src/plugin_examples/plugin_api.md b/src/plugin_examples/plugin_api.md deleted file mode 100644 index 6954f15b52d7900e14a493376423c61697dacb82..0000000000000000000000000000000000000000 --- a/src/plugin_examples/plugin_api.md +++ /dev/null @@ -1,416 +0,0 @@ -# Plugin APIs - -## window.interactiveViewer - -### metadata - -#### selectedTemplateBSubject - -BehaviourSubject that emits a TemplateDescriptor object whenever a template is selected. Emits null onInit. - -#### selectedParcellationBSubject - -BehaviourSubject that emits a ParcellationDescriptor object whenever a parcellation is selected. n.b. selecting a new template automatically select the first available parcellation. Emits null onInit. - -#### selectedRegionsBSubject - -BehaviourSubject that emits an Array of RegionDescriptor objects whenever the list of selected regions changes. Emits empty array onInit. - -#### loadedTemplates - -Array of TemplateDescriptor objects. Loaded asynchronously onInit. - -#### layersRegionLabelIndexMap - -Map of layer name to Map of labelIndex (used by neuroglancer and nehuba) to the corresponding RegionDescriptor object. - -### viewerHandle - -> **nb** `viewerHandle` may be undefined at any time (user be yet to select an atlas, user could have unloaded an atlas. ...etc) - -#### setNavigationLoc(coordinates, realspace?:boolean) - -Function that teleports the navigation state to coordinates : [x:number,y:number,z:number]. Optional arg determine if the set of coordinates is in realspace (default) or voxelspace. - -#### moveToNavigationLoc(coordinates,realspace?:boolean) - -same as *setNavigationLoc(coordinates,realspace?)*, except the action is carried out over 500ms. - -#### setNavigationOri(ori) - -(NYI) Function that sets the orientation state of the viewer. - -#### moveToNavigationOri(ori) - -(NYI) same as *setNavigationOri*, except the action is carried out over 500ms. - -#### showSegment(labelIndex) - -Function that shows a specific segment. Will trigger *selectedRegionsBSubject*. - -#### hideSegment(labelIndex) - -Function that hides a specific segment. Will trigger *selectRegionsBSubject* - -#### showAllSegments() - -Function that shows all segments. Will trigger *selectRegionsBSubject* - -#### hideAllSegments() -Function that hides all segments. Will trigger *selectRegionBSubject* - -#### getLayersSegmentColourMap() - -Call to get Map of layer name to Map of label index to colour map - -#### applyLayersColourMap - -Function that applies a custom colour map. - -#### loadLayer(layerObject) - -Function that loads *ManagedLayersWithSpecification* directly to neuroglancer. Returns the values of the object successfully added. **n.b.** advanced feature, will likely break other functionalities. **n.b.** if the layer name is already taken, the layer will not be added. - -```javascript -const obj = { - 'advanced layer' : { - type : 'image', - source : 'nifti://http://example.com/data/nifti.nii', - }, - 'advanced layer 2' : { - type : 'mesh', - source : 'vtk://http://example.com/data/vtk.vtk' - } -} -const returnValue = window.interactiveViewer.viewerHandle.loadLayer(obj) -/* loads two layers, an image nifti layer and a mesh vtk layer */ - -console.log(returnValue) -/* prints - -[{ - type : 'image', - source : 'nifti...' -}, -{ - type : 'mesh', - source : 'vtk...' -}] -*/ -``` - -#### removeLayer(layerObject) - -Function that removes *ManagedLayersWithSpecification*, returns an array of the names of the layers removed. - -**n.b.** advanced feature. may break other functionalities. - -```js -const obj = { - 'name' : /^PMap/ -} -const returnValue = window.interactiveViewer.viewerHandle.removeLayer(obj) - -console.log(returnValue) -/* prints -['PMap 001','PMap 002'] -*/ -``` - -#### add3DLandmarks(landmarks) - -adds landmarks to both the perspective view and slice view. - -_input_ - -| input | type | desc | -| --- | --- | --- | -| landmarks | array | an array of `landmarks` to be rendered by the viewer | - -A landmark object consist of the following keys: - -| key | type | desc | required | -| --- | --- | --- | --- | -| id | string | id of the landmark | yes | -| name | string | name of the landmark | | -| position | [number, number, number] | position (in mm) | yes | -| color | [number, number, number] | rgb of the landmark | | - - -```js -const landmarks = [{ - id : `fzj-xg-jugex-1`, - position : [0,0,0] -},{ - id : `fzj-xg-jugex-2`, - position : [22,27,-1], - color: [255, 0, 255] -}] -window.interactiveViewer.viewerHandle.add3DLandmarks(landmarks) - -/* adds landmarks in perspective view and slice view */ -``` - -#### remove3DLandmarks(IDs) - -removes the landmarks by their IDs - -```js -window.interactiveViewer.viewerHandle - .remove3DLandmarks(['fzj-xg-jugex-1', 'fzj-xg-jugex-2']) -/* removes the landmarks added above */ -``` - -#### setLayerVisibility(layerObject, visible) - -Function that sets the visibility of a layer. Returns the names of all the layers that are affected as an Array of string. - -```js -const obj = { - 'type' : 'segmentation' -} - -window.interactiveViewer.viewerHandle.setLayerVisibility(obj,false) - -/* turns off all the segmentation layers */ -``` - -#### mouseEvent - -Subject that emits an object shaped `{ eventName : string, event: event }` when a user triggers a mouse event on the viewer. - - -#### mouseOverNehuba - -**deprecating** use mouseOverNehubaUI instead - -BehaviourSubject that emits an object shaped `{ nehubaOutput : number | null, foundRegion : RegionDescriptor | null }` - -#### mouseOverNehubaUI - -`Observable<{ landmark, segments, customLandmark }>`. - -**nb** it is a known issue that if customLandmarks are destroyed/created while user mouse over the custom landmark this observable will emit `{ customLandmark: null }` - -### uiHandle - -#### getModalHandler() - -returns a modalHandler object, which has the following methods/properties: - -##### hide() - -Dynamically hides the modal - -##### show() - -Shows the modal - -##### title - -title of the modal (String) - -##### body - -body of the modal shown (String) - -##### footer - -footer of the modal (String) - -##### dismissable - -whether the modal is dismissable on click backdrop/esc key (Boolean) - -*n.b. if true, users will not be able to interact with the viewer unless you specifically call `handler.hide()`* - -#### launchNewWidget(manifest) - -returns a Promise. expects a JSON object, with the same key value as a plugin manifest. the *name* key must be unique, or the promise will be rejected. - -#### getUserInput(config) - -returns a Promise, resolves when user confirms, rejects when user cancels. expects config object object with the following structure: - -```javascript -const config = { - "title": "Title of the modal", // default: "Message" - "message":"Message to be seen by the user.", // default: "" - "placeholder": "Start typing here", // default: "Type your response here" - "defaultValue": "42" // default: "" - "iconClass":"fas fa-save" // default fas fa-save, set to falsy value to disable -} -``` - -#### getUserConfirmation(config) - -returns a Promise, resolves when user confirms, rejects when user cancels. expects config object object with the following structure: - -```javascript -const config = { - "title": "Title of the modal", // default: "Message" - "message":"Message to be seen by the user." // default: "" -} -``` - -#### getUserToSelectARegion(message) - -**To be deprecated** - -_input_ - -| input | type | desc | -| --- | --- | --- | -| message | `string` | human readable message displayed to the user | -| spec.type | `'POINT'` `'PARCELLATION_REGION'` **default** | type of region to be returned. | - -_returns_ - -`Promise`, resolves to return array of region clicked, rejects with error object `{ userInitiated: boolean }` - -Requests user to select a region of interest. Resolving to the region selected by the user. Rejects if either user cancels by pressing `Esc` or `Cancel`, or by developer calling `cancelPromise` - -#### getUserToSelectRoi(message, spec) - -_input_ - -| input | type | desc | -| --- | --- | --- | -| message | `string` | human readable message displayed to the user | -| spec.type | `POINT` `PARCELLATION_REGION` | type of ROI to be returned. | - -_returns_ - -`Promise` - -**resolves**: return `{ type, payload }`. `type` is the same as `spec.type`, and `payload` differs depend on the type requested: - -| type | payload | example | -| --- | --- | --- | -| `POINT` | array of number in mm | `[12.2, 10.1, -0.3]` | -| `PARCELLATION_REGOIN` | non empty array of region selected | `[{ "layer": { "name" : " viewer specific layer name " }, "segment": {} }]` | - -**rejects**: with error object `{ userInitiated: boolean }` - -Requests user to select a region of interest. If the `spec.type` input is missing, it is assumed to be `'PARCELLATION_REGION'`. Resolving to the region selected by the user. Rejects if either user cancels by pressing `Esc` or `Cancel`, or by developer calling `cancelPromise` - -#### cancelPromise(promise) - -returns `void` - -_input_ - -| input | type | desc | -| --- | --- | --- | -| promise | `Promise` | Reference to the __exact__ promise returned by `uiHnandle` methods | - -Cancel the request to select a parcellation region. - -_usage example_ - -```javascript - -(() => { - const pr = interactive.uiHandle.getUserToSelectARegion(`webJuGEx would like you to select a region`) - - pr.then(region => { }) - .catch(console.warn) - - /* - * do NOT do - * - * const pr = interactive.uiHandle.getUserToSelectARegion(`webJuGEx would like you to select a region`) - * .then(region => { }) - * -catch(console.warn) - * - * the promise passed to `cancelPromise` must be the exact promise returned. - * by chaining then/catch, a new reference is returned - */ - - setTimeout(() => { - try { - interactive.uiHandle.cancelPromise(pr) - } catch (e) { - // if the promise has been fulfilled (by resolving or user cancel), cancelPromise will throw - } - }, 5000) -})() -``` - -### pluginControl - -#### loadExternalLibraries([LIBRARY_NAME_1,LIBRARY_NAME_2]) - -Function that loads external libraries. Pass the name of the libraries as an Array of string, and returns a Promise. When promise resolves, the libraries are loaded. - -**n.b.** while unlikely, there is a possibility that multiple requests to load external libraries in quick succession can cause the promise to resolve before the library is actually loaded. - -```js -const currentlySupportedLibraries = ['jquery@2','jquery@3','webcomponentsLite@1.1.0','react@16','reactdom@16','vue@2.5.16'] - -window.interactivewViewer.loadExternalLibraries(currentlySupportedLibraries) - .then(() => { - /* loaded */ - }) - .catch(e=>console.warn(e)) - -``` - -#### unloadExternalLibraries([LIBRARY_NAME_1,LIBRARY_NAME_2]) - -unloading the libraries (should be called on shutdown). - -#### *[PLUGINNAME]* - -returns a plugin handler. This would be how to interface with the plugins. - -##### blink() - -Function that causes the floating widget to blink, attempt to grab user attention (silently fails if called on startup). - -##### setProgressIndicator(val:number|null) - -Set the progress of the plugin. Useful for giving user feedbacks on the progress of a long running process. Call the function with null to unset the progress. - -##### shutdown() - -Function that causes the widget to shutdown dynamically. (triggers onShutdown callback, silently fails if called on startup) - -##### onShutdown(callback) - -Attaches a callback function, which is called when the plugin is shutdown. - -##### initState - -passed from `manifest.json`. Useful for setting initial state of the plugin. Can be any JSON valid value (array, object, string). - -##### initStateUrl - -passed from `manifest.json`. Useful for setting initial state of the plugin. Can be any JSON valid value (array, object, string). - -##### setInitManifestUrl(url|null) - -set/unset the url for a manifest json that will be fetched on atlas viewer startup. the argument should be a valid URL, has necessary CORS header, and returns a valid manifest json file. null will unset the search param. Useful for passing/preserving state. If called multiple times, the last one will take effect. - -```js -const pluginHandler = window.interactiveViewer.pluginControl[PLUGINNAME] - -const subscription = window.interactiveViewer.metadata.selectedTemplateBSubject.subscribe(template=>console.log(template)) - -fetch(`http://YOUR_BACKEND.com/API_ENDPOINT`) - .then(data=>pluginHandler.blink(20)) - -pluginHandler.onShutdown(()=>{ - subscription.unsubscribe() -}) -``` - ------- - -## window.nehubaViewer - -nehuba object, exposed if developer would like to use it - -## window.viewer - -neuroglancer object, exposed if developer would like to use it \ No newline at end of file diff --git a/src/routerModule/module.ts b/src/routerModule/module.ts index 1053deeb997244e486942db154f13e3d1c057f51..c5e4845005c2fcdc4db1f5d1f68151bae4754a50 100644 --- a/src/routerModule/module.ts +++ b/src/routerModule/module.ts @@ -2,6 +2,7 @@ import { APP_BASE_HREF } from "@angular/common"; import { NgModule } from "@angular/core"; import { RouterModule } from '@angular/router' import { RouterService } from "./router.service"; +import { RouteStateTransformSvc } from "./routeStateTransform.service"; import { routes } from "./util"; @@ -16,7 +17,8 @@ import { routes } from "./util"; provide: APP_BASE_HREF, useValue: '/' }, - RouterService + RouterService, + RouteStateTransformSvc, ], exports:[ RouterModule diff --git a/src/routerModule/parseRouteToTmplParcReg.spec.ts b/src/routerModule/parseRouteToTmplParcReg.spec.ts deleted file mode 100644 index d81c9491c540006a068f13ad7b8e573f645d0345..0000000000000000000000000000000000000000 --- a/src/routerModule/parseRouteToTmplParcReg.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { parseSearchParamForTemplateParcellationRegion } from './parseRouteToTmplParcReg' - -const url = `/a:juelich:iav:atlas:v1.0.0:1/t:minds:core:referencespace:v1.0.0:dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2/p:minds:core:parcellationatlas:v1.0.0:94c1125b-b87e-45e4-901c-00daee7f2579-26/@:0.0.0.-W000..2_ZG29.-ASCS.2-8jM2._aAY3..BSR0..0.1w4W0~.0..1jtG` - -const fakeState = { -} -const fakeTmpl = { - '@id': 'foobar' -} -const fakeParc = { - '@id': 'buzbaz' -} -const fakeRegions = [{ - ngId: 'foo', - labelIndex: 152 -}] - -describe('parseRouteToTmplParcReg.ts', () => { - describe('> parseSearchParamForTemplateParcellationRegion', () => { - it('> parses selected region properly', () => { - - }) - }) -}) \ No newline at end of file diff --git a/src/routerModule/parseRouteToTmplParcReg.ts b/src/routerModule/parseRouteToTmplParcReg.ts deleted file mode 100644 index a1ed2a7c47003fbaae169cc49bdd0b7e8be0e3bb..0000000000000000000000000000000000000000 --- a/src/routerModule/parseRouteToTmplParcReg.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { getGetRegionFromLabelIndexId } from 'src/util/fn' -import { serialiseParcellationRegion } from "common/util" -import { decodeToNumber, separator } from './cipher' - -import { - TUrlAtlas, - TUrlPathObj, -} from './type' -import { UrlTree } from '@angular/router' - - -export const PARSE_ERROR = { - TEMPALTE_NOT_SET: 'TEMPALTE_NOT_SET', - TEMPLATE_NOT_FOUND: 'TEMPLATE_NOT_FOUND', - PARCELLATION_NOT_UPDATED: 'PARCELLATION_NOT_UPDATED', -} - -export const encodeId = (id: string) => id && id.replace(/\//g, ':') -export const decodeId = (codedId: string) => codedId && codedId.replace(/:/g, '/') - -export function parseSearchParamForTemplateParcellationRegion(obj: TUrlPathObj<string[], TUrlAtlas<string[]>>, fullPath: UrlTree, state: any, warnCb: (arg: string) => void) { - - /** - * TODO if search param of either template or parcellation is incorrect, wrong things are searched - */ - - - const templateSelected = (() => { - const { fetchedTemplates } = state.viewerState - - const searchedId = obj.t && decodeId(obj.t[0]) - - if (!searchedId) return null - const templateToLoad = fetchedTemplates.find(template => (template['@id'] || template['fullId']) === searchedId) - if (!templateToLoad) { throw new Error(PARSE_ERROR.TEMPLATE_NOT_FOUND) } - return templateToLoad - })() - - const parcellationSelected = (() => { - if (!templateSelected) return null - const searchedId = obj.p && decodeId(obj.p[0]) - - const parcellationToLoad = templateSelected.parcellations.find(parcellation => (parcellation['@id'] || parcellation['fullId']) === searchedId) - if (!parcellationToLoad) { - warnCb(`parcellation with id ${searchedId} not found... load the first parc instead`) - } - return parcellationToLoad || templateSelected.parcellations[0] - })() - - /* selected regions */ - - const regionsSelected = (() => { - if (!parcellationSelected) return [] - - // TODO deprecate. Fallback (defaultNgId) (should) already exist - // if (!viewerState.parcellationSelected.updated) throw new Error(PARCELLATION_NOT_UPDATED) - - const getRegionFromlabelIndexId = getGetRegionFromLabelIndexId({ parcellation: parcellationSelected }) - /** - * either or both parcellationToLoad and .regions maybe empty - */ - - try { - const json = obj.r - ? { [obj.r[0]]: obj.r[1] } - : JSON.parse(fullPath.queryParams['cRegionsSelected'] || '{}') - - const selectRegionIds = [] - - for (const ngId in json) { - const val = json[ngId] - const labelIndicies = val.split(separator).map(n => { - try { - return decodeToNumber(n) - } catch (e) { - /** - * TODO poisonsed encoded char, send error message - */ - return null - } - }).filter(v => !!v) - for (const labelIndex of labelIndicies) { - selectRegionIds.push( serialiseParcellationRegion({ ngId, labelIndex }) ) - } - } - return selectRegionIds - .map(labelIndexId => { - const region = getRegionFromlabelIndexId({ labelIndexId }) - if (!region) { - // cb && cb({ type: ID_ERROR, message: `region with id ${labelIndexId} not found, and will be ignored.` }) - } - return region - }) - .filter(r => !!r) - - } catch (e) { - /** - * parsing cRegionSelected error - */ - // cb && cb({ type: DECODE_CIPHER_ERROR, message: `parsing cRegionSelected error ${e.toString()}` }) - } - return [] - })() - - return { - templateSelected, - parcellationSelected, - regionsSelected, - } -} diff --git a/src/routerModule/routeStateTransform.service.spec.ts b/src/routerModule/routeStateTransform.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..590619e9e2f428f4ebd07591e14d6e911862701c --- /dev/null +++ b/src/routerModule/routeStateTransform.service.spec.ts @@ -0,0 +1,199 @@ +import { TestBed } from "@angular/core/testing" +import { of } from "rxjs" +import { SAPI } from "src/atlasComponents/sapi" +import { RouteStateTransformSvc } from "./routeStateTransform.service" +import { DefaultUrlSerializer } from "@angular/router" +import * as nehubaConfigService from "src/viewerModule/nehuba/config.service" +import { atlasSelection, userInteraction } from "src/state" +import { encodeNumber } from "./cipher" + +const serializer = new DefaultUrlSerializer() + +describe("> routeStateTransform.service.ts", () => { + describe("> RouteStateTransformSvc", () => { + let svc: RouteStateTransformSvc + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + RouteStateTransformSvc, + { + provide: SAPI, + useValue: { + atlases$: of([]), + getSpaceDetail: jasmine.createSpy('getSpaceDetail'), + getParcDetail: jasmine.createSpy('getParcDetail'), + getParcRegions: jasmine.createSpy('getParcRegions'), + } + } + ] + }) + svc = TestBed.inject(RouteStateTransformSvc) + }) + + describe("> cvtRouteToState", () => { + describe("> decode region", () => { + + }) + + describe("> decode sv", () => { + let sv: string[] + beforeEach(async () => { + const searchParam = new URLSearchParams() + searchParam.set('standaloneVolumes', '["precomputed://https://object.cscs.ch/v1/AUTH_08c08f9f119744cbbf77e216988da3eb/imgsvc-46d9d64f-bdac-418e-a41b-b7f805068c64"]') + const url = `foo/bar?${searchParam.toString()}` + const urlTree = serializer.parse(url) + const state = await svc.cvtRouteToState(urlTree) + sv = state["[state.atlasSelection]"].standAloneVolumes + }) + + it('> sv should be truthy', () => { + expect(sv).toBeTruthy() + }) + + it('> sv should be array', () => { + expect( + Array.isArray(sv) + ).toBeTrue() + }) + + it('> sv should have length 1', () => { + expect(sv.length).toEqual(1) + }) + + it('> sv[0] should be expected value', () => { + expect(sv[0]).toEqual( + 'precomputed://https://object.cscs.ch/v1/AUTH_08c08f9f119744cbbf77e216988da3eb/imgsvc-46d9d64f-bdac-418e-a41b-b7f805068c64' + ) + }) + }) + + describe("> decode navigation", () => { + beforeEach(() => { + }) + // it('> if not present, should show something palatable', async () => { + + // const scale = 0.25 + + // const url = `/a:juelich:iav:atlas:v1.0.0:1/t:minds:core:referencespace:v1.0.0:dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2/p:minds:core:parcellationatlas:v1.0.0:94c1125b-b87e-45e4-901c-00daee7f2579-290` + + // const urlTree = serializer.parse(url) + // const state = await svc.cvtRouteToState(urlTree) + + // const { navigation } = state["[state.atlasSelection]"] + // const { + // orientation, + // perspectiveOrientation, + // position, + // zoom, + // perspectiveZoom, + // } = navigation + + // expect(orientation).toEqual([0,0,0,1]) + // expect(perspectiveOrientation).toEqual([ + // 0.3140767216682434, + // -0.7418519854545593, + // 0.4988985061645508, + // -0.3195493221282959 + // ]) + // expect(position).toEqual([0,0,0]) + // expect(zoom).toEqual(350000 * scale) + // expect(perspectiveZoom).toEqual(1922235.5293810747 * scale) + // }) + }) + }) + + describe("> cvtStateToRoute", () => { + + describe('> should be able encode region properly', () => { + let getRegionLabelIndexSpy: jasmine.Spy = jasmine.createSpy('getRegionLabelIndex') + let getParcNgId: jasmine.Spy = jasmine.createSpy('getParcNgId') + let atlasSelectionSpy: Record<string, jasmine.Spy> = { + selectedAtlas: jasmine.createSpy('selectedAtlas'), + selectedParcellation: jasmine.createSpy('selectedParcellation'), + selectedTemplate: jasmine.createSpy('selectedTemplate'), + selectedRegions: jasmine.createSpy('selectedRegions'), + standaloneVolumes: jasmine.createSpy('standaloneVolumes'), + navigation: jasmine.createSpy('navigation'), + } + + let userInteractionSpy: Record<string, jasmine.Spy> = { + selectedFeature: jasmine.createSpy('selectedFeature') + } + + const altasObj = {"@id": 'foo-bar-a'} + const templObj = {"@id": 'foo-bar-t'} + const parcObj = {"@id": 'foo-bar-p'} + const regions = [{}] + const standAloneVolumes = [] + const navigation = null + + beforeEach(() => { + spyOnProperty(nehubaConfigService, 'getRegionLabelIndex').and.returnValue(getRegionLabelIndexSpy) + spyOnProperty(nehubaConfigService, 'getParcNgId').and.returnValue(getParcNgId) + spyOnProperty(atlasSelection, 'selectors').and.returnValue(atlasSelectionSpy) + spyOnProperty(userInteraction, 'selectors').and.returnValue(userInteractionSpy) + + atlasSelectionSpy.selectedAtlas.and.returnValue(altasObj) + atlasSelectionSpy.selectedParcellation.and.returnValue(templObj) + atlasSelectionSpy.selectedTemplate.and.returnValue(parcObj) + atlasSelectionSpy.selectedRegions.and.returnValue(regions) + atlasSelectionSpy.standaloneVolumes.and.returnValue(standAloneVolumes) + atlasSelectionSpy.navigation.and.returnValue(navigation) + + userInteractionSpy.selectedFeature.and.returnValue(null) + }) + + afterEach(() => { + getRegionLabelIndexSpy.calls.reset() + getParcNgId.calls.reset() + for (const spyRecord of [atlasSelectionSpy, userInteractionSpy]) { + for (const key in spyRecord) { + spyRecord[key].calls.reset() + } + } + }) + + it('> calls correct functions', () => { + + getRegionLabelIndexSpy.and.returnValue(11) + getParcNgId.and.returnValue('foo-bar') + + const state = {} + const svc = TestBed.inject(RouteStateTransformSvc) + const s = svc.cvtStateToRoute(state as any) + + for (const key in atlasSelectionSpy) { + expect(atlasSelectionSpy[key]).toHaveBeenCalledTimes(1) + } + }) + + it('> regular ngId', () => { + const ngId = 'foobar' + const labelIndex = 124 + + getRegionLabelIndexSpy.and.returnValue(labelIndex) + getParcNgId.and.returnValue(ngId) + + const state = {} + const svc = TestBed.inject(RouteStateTransformSvc) + const s = svc.cvtStateToRoute(state as any) + + expect(s).toContain(`r:${ngId}::${encodeNumber(labelIndex, { float: false })}`) + }) + + it('> ngId containing ()', () => { + const ngId = 'foobar(1)' + const labelIndex = 124 + + getRegionLabelIndexSpy.and.returnValue(labelIndex) + getParcNgId.and.returnValue(ngId) + + const state = {} + const svc = TestBed.inject(RouteStateTransformSvc) + const s = svc.cvtStateToRoute(state as any) + expect(s).toContain(`r:foobar%25281%2529::${encodeNumber(labelIndex, { float: false })}`) + }) + }) + }) + }) +}) diff --git a/src/routerModule/routeStateTransform.service.ts b/src/routerModule/routeStateTransform.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..dc7da45d8a9eed4d3b8c4edbe31c409864d09741 --- /dev/null +++ b/src/routerModule/routeStateTransform.service.ts @@ -0,0 +1,286 @@ +import { Injectable } from "@angular/core"; +import { UrlSegment, UrlTree } from "@angular/router"; +import { map } from "rxjs/operators"; +import { SAPI, SapiRegionModel } from "src/atlasComponents/sapi"; +import { atlasSelection, defaultState, MainState, plugins, userInteraction } from "src/state"; +import { getParcNgId, getRegionLabelIndex } from "src/viewerModule/nehuba/config.service"; +import { decodeToNumber, encodeNumber, encodeURIFull, separator } from "./cipher"; +import { TUrlAtlas, TUrlPathObj, TUrlStandaloneVolume } from "./type"; +import { decodePath, encodeId, decodeId, endcodePath } from "./util"; + +@Injectable() +export class RouteStateTransformSvc { + + static GetOneAndOnlyOne<T>(arr: T[]): T{ + if (!arr || arr.length === 0) return null + if (arr.length > 1) throw new Error(`expecting exactly 1 item, got ${arr.length}`) + return arr[0] + } + + constructor(private sapi: SAPI){} + + private async getATPR(obj: TUrlPathObj<string[], TUrlAtlas<string[]>>){ + const selectedAtlasId = decodeId( RouteStateTransformSvc.GetOneAndOnlyOne(obj.a) ) + const selectedTemplateId = decodeId( RouteStateTransformSvc.GetOneAndOnlyOne(obj.t) ) + const selectedParcellationId = decodeId( RouteStateTransformSvc.GetOneAndOnlyOne(obj.p) ) + const selectedRegionIds = obj.r + + if (!selectedAtlasId || !selectedTemplateId || !selectedParcellationId) { + return {} + } + + const [ + selectedAtlas, + selectedTemplate, + selectedParcellation, + allParcellationRegions = [] + ] = await Promise.all([ + this.sapi.atlases$.pipe( + map(atlases => atlases.find(atlas => atlas["@id"] === selectedAtlasId)) + ).toPromise(), + this.sapi.getSpaceDetail(selectedAtlasId, selectedTemplateId, { priority: 10 }).toPromise(), + this.sapi.getParcDetail(selectedAtlasId, selectedParcellationId, { priority: 10 }).toPromise(), + this.sapi.getParcRegions(selectedAtlasId, selectedParcellationId, selectedTemplateId, { priority: 10 }).toPromise(), + ]) + + const ngIdToRegionMap: Map<string, Map<number, SapiRegionModel[]>> = new Map() + + for (const region of allParcellationRegions) { + const ngId = getParcNgId(selectedAtlas, selectedTemplate, selectedParcellation, region) + if (!ngIdToRegionMap.has(ngId)) { + ngIdToRegionMap.set(ngId, new Map()) + } + const map = ngIdToRegionMap.get(ngId) + + const idx = getRegionLabelIndex(selectedAtlas, selectedTemplate, selectedParcellation, region) + if (!map.has(idx)) { + map.set(idx, []) + } + map.get(idx).push(region) + } + + const selectedRegions = (() => { + if (!selectedRegionIds) return [] + /** + * assuming only 1 selected region + * if this assumption changes, iterate over array of selectedRegionIds + */ + const json = { [selectedRegionIds[0]]: selectedRegionIds[1] } + + for (const ngId in json) { + if (!ngIdToRegionMap.has(ngId)) { + console.error(`could not find matching map for ${ngId}`) + continue + } + + const map = ngIdToRegionMap.get(ngId) + + const val = json[ngId] + const labelIndicies = val.split(separator).map(n => { + try { + return decodeToNumber(n) + } catch (e) { + /** + * TODO poisonsed encoded char, send error message + */ + return null + } + }).filter(v => !!v) + + return labelIndicies.map(idx => map.get(idx) || []).flatMap(v => v) + } + return [] + })() + + return { + selectedAtlas, + selectedTemplate, + selectedParcellation, + selectedRegions, + allParcellationRegions + } + } + + async cvtRouteToState(fullPath: UrlTree) { + + const returnState: MainState = defaultState + const pathFragments: UrlSegment[] = fullPath.root.hasChildren() + ? fullPath.root.children['primary'].segments + : [] + + const returnObj: Partial<TUrlPathObj<string[], unknown>> = {} + for (const f of pathFragments) { + const { key, val } = decodePath(f.path) || {} + if (!key || !val) continue + returnObj[key] = val + } + + // nav obj is almost always defined, regardless if standaloneVolume or not + const cViewerState = returnObj['@'] && returnObj['@'][0] + let parsedNavObj: MainState['[state.atlasSelection]']['navigation'] + if (cViewerState) { + try { + const [ cO, cPO, cPZ, cP, cZ ] = cViewerState.split(`${separator}${separator}`) + const o = cO.split(separator).map(s => decodeToNumber(s, {float: true})) + const po = cPO.split(separator).map(s => decodeToNumber(s, {float: true})) + const pz = decodeToNumber(cPZ) + const p = cP.split(separator).map(s => decodeToNumber(s)) + const z = decodeToNumber(cZ) + parsedNavObj = { + orientation: o, + perspectiveOrientation: po, + perspectiveZoom: pz, + position: p, + zoom: z, + } + } catch (e) { + /** + * TODO Poisoned encoded char + * send error message + */ + console.error(`cannot parse navigation state`, e) + } + } + + // pluginState should always be defined, regardless if standalone volume or not + const pluginStates = fullPath.queryParams['pl'] + if (pluginStates) { + try { + const arrPluginStates: string[] = JSON.parse(pluginStates) + if (arrPluginStates.length > 1) throw new Error(`can only initialise one plugin at a time`) + returnState["[state.plugins]"].initManifests = { + [plugins.INIT_MANIFEST_SRC]: arrPluginStates + } + } catch (e) { + /** + * parsing plugin error + */ + console.error(`parse plugin states error`, e, pluginStates) + } + } + + // If sv (standaloneVolume is defined) + // only load sv in state + // ignore all other params + // /#/sv:%5B%22precomputed%3A%2F%2Fhttps%3A%2F%2Fobject.cscs.ch%2Fv1%2FAUTH_08c08f9f119744cbbf77e216988da3eb%2Fimgsvc-46d9d64f-bdac-418e-a41b-b7f805068c64%22%5D + const standaloneVolumes = fullPath.queryParams['standaloneVolumes'] + try { + const parsedArr = JSON.parse(standaloneVolumes) + if (!Array.isArray(parsedArr)) throw new Error(`Parsed standalone volumes not of type array`) + + returnState["[state.atlasSelection]"].standAloneVolumes = parsedArr + returnState["[state.atlasSelection]"].navigation = parsedNavObj + return returnState + } catch (e) { + // if any error occurs, parse rest per normal + } + + // try to get feature + try { + if (returnObj.f && returnObj.f.length === 1) { + const decodedFeatId = decodeId(returnObj.f[0]) + const feature = await this.sapi.getFeature(decodedFeatId).detail$.toPromise() + returnState["[state.userInteraction]"].selectedFeature = feature + } + } catch (e) { + console.error(`fetching selected feature error`) + } + + try { + const { selectedAtlas, selectedParcellation, selectedRegions = [], selectedTemplate, allParcellationRegions } = await this.getATPR(returnObj as TUrlPathObj<string[], TUrlAtlas<string[]>>) + returnState["[state.atlasSelection]"].selectedAtlas = selectedAtlas + returnState["[state.atlasSelection]"].selectedParcellation = selectedParcellation + returnState["[state.atlasSelection]"].selectedTemplate = selectedTemplate + returnState["[state.atlasSelection]"].selectedRegions = selectedRegions || [] + returnState["[state.atlasSelection]"].selectedParcellationAllRegions = allParcellationRegions || [] + returnState["[state.atlasSelection]"].navigation = parsedNavObj + } catch (e) { + // if error, show error on UI? + console.error(`parse template, parc, region error`, e) + } + return returnState + } + + cvtStateToRoute(_state: MainState) { + + /** + * need to create new references here + * or else, the memoized selector will spit out undefined + */ + const state:MainState = JSON.parse(JSON.stringify(_state)) + + const selectedAtlas = atlasSelection.selectors.selectedAtlas(state) + const selectedParcellation = atlasSelection.selectors.selectedParcellation(state) + const selectedTemplate = atlasSelection.selectors.selectedTemplate(state) + + const selectedRegions = atlasSelection.selectors.selectedRegions(state) + const standaloneVolumes = atlasSelection.selectors.standaloneVolumes(state) + const navigation = atlasSelection.selectors.navigation(state) + const selectedFeature = userInteraction.selectors.selectedFeature(state) + + const searchParam = new URLSearchParams() + + let cNavString: string + if (navigation) { + const { orientation, perspectiveOrientation, perspectiveZoom, position, zoom } = navigation + if (orientation && perspectiveOrientation && perspectiveZoom && position && zoom) { + cNavString = [ + orientation.map((n: number) => encodeNumber(n, {float: true})).join(separator), + perspectiveOrientation.map(n => encodeNumber(n, {float: true})).join(separator), + encodeNumber(Math.floor(perspectiveZoom)), + Array.from(position).map((v: number) => Math.floor(v)).map(n => encodeNumber(n)).join(separator), + encodeNumber(Math.floor(zoom)), + ].join(`${separator}${separator}`) + } + } + + // encoding selected regions + let selectedRegionsString: string + if (selectedRegions.length === 1) { + const region = selectedRegions[0] + const labelIndex = getRegionLabelIndex(selectedAtlas, selectedTemplate, selectedParcellation, region) + + const ngId = getParcNgId(selectedAtlas, selectedTemplate, selectedParcellation, region) + selectedRegionsString = `${ngId}::${encodeNumber(labelIndex, { float: false })}` + } + let routes: TUrlPathObj<string, TUrlAtlas<string>> | TUrlPathObj<string, TUrlStandaloneVolume<string>> + + routes = { + // for atlas + a: selectedAtlas && encodeId(selectedAtlas['@id']), + // for template + t: selectedTemplate && encodeId(selectedTemplate['@id']), + // for parcellation + p: selectedParcellation && encodeId(selectedParcellation['@id']), + // for regions + r: selectedRegionsString && encodeURIFull(selectedRegionsString), + // nav + ['@']: cNavString, + // showing dataset + f: selectedFeature && encodeId(selectedFeature["@id"]) + } + + /** + * if any params needs to overwrite previosu routes, put them here + */ + if (standaloneVolumes && Array.isArray(standaloneVolumes) && standaloneVolumes.length > 0) { + searchParam.set('standaloneVolumes', JSON.stringify(standaloneVolumes)) + routes = { + // nav + ['@']: cNavString, + } as TUrlPathObj<string, TUrlStandaloneVolume<string>> + } + + const routesArr: string[] = [] + for (const key in routes) { + if (!!routes[key]) { + const segStr = endcodePath(key, routes[key]) + routesArr.push(segStr) + } + } + + return searchParam.toString() === '' + ? routesArr.join('/') + : `${routesArr.join('/')}?${searchParam.toString()}` + } +} diff --git a/src/routerModule/router.service.spec.ts b/src/routerModule/router.service.spec.ts index a06a700ff5c0d726381c0ecb9e3de24d08170bc7..f85b0beeab988b802bbff64dae27e0ffc34fc042 100644 --- a/src/routerModule/router.service.spec.ts +++ b/src/routerModule/router.service.spec.ts @@ -1,199 +1,193 @@ import { APP_BASE_HREF, Location } from "@angular/common" import { discardPeriodicTasks, fakeAsync, TestBed, tick } from "@angular/core/testing" -import { Router } from "@angular/router" -import { RouterTestingModule } from '@angular/router/testing' +import { DefaultUrlSerializer, NavigationEnd, Router } from "@angular/router" import { MockStore, provideMockStore } from "@ngrx/store/testing" import { cold } from "jasmine-marbles" -import { of } from "rxjs" -import { PureContantService } from "src/util" +import { of, Subject } from "rxjs" +import { SAPI } from "src/atlasComponents/sapi" import { RouterService } from "./router.service" +import { RouteStateTransformSvc } from "./routeStateTransform.service" import * as util from './util' -const { routes, DummyCmp } = util -const dummyPureConstantService = { - allFetchingReady$: of(true) +const { DummyCmp } = util + +let cvtStateToRouteSpy: jasmine.Spy +let cvtRouteToStateSpy: jasmine.Spy +const mockRouter = { + events: new Subject(), + parseUrl: (url: string) => { + return new DefaultUrlSerializer().parse(url) + }, + url: '/', + navigate: jasmine.createSpy('navigate'), + navigateByUrl: jasmine.createSpy('navigateByUrl') } -let cvtStateToHashedRoutesSpy: jasmine.Spy -let cvtFullRouteToStateSpy: jasmine.Spy -let location: Location -let router: Router - describe('> router.service.ts', () => { describe('> RouterService', () => { beforeEach(() => { - cvtStateToHashedRoutesSpy = jasmine.createSpy('cvtStateToHashedRoutesSpy') - cvtFullRouteToStateSpy = jasmine.createSpy('cvtFullRouteToState') - - spyOnProperty(util, 'cvtStateToHashedRoutes').and.returnValue(cvtStateToHashedRoutesSpy) - spyOnProperty(util, 'cvtFullRouteToState').and.returnValue(cvtFullRouteToStateSpy) + cvtStateToRouteSpy = jasmine.createSpy('cvtStateToRouteSpy') + cvtRouteToStateSpy = jasmine.createSpy('cvtFullRouteToState') TestBed.configureTestingModule({ - imports: [ - RouterTestingModule.withRoutes(routes, { - useHash: true - }), - ], + imports: [], declarations: [ DummyCmp, ], providers: [ provideMockStore(), - { - provide: PureContantService, - useValue: dummyPureConstantService - }, { provide: APP_BASE_HREF, useValue: '/' + }, + { + provide: SAPI, + useValue: { + atlases$: of([]) + } + }, + { + provide: RouteStateTransformSvc, + useValue: { + cvtRouteToState: cvtRouteToStateSpy, + cvtStateToRoute: cvtStateToRouteSpy + } + }, + { + provide: Router, + useValue: mockRouter } ] }) }) afterEach(() => { - cvtStateToHashedRoutesSpy.calls.reset() - cvtFullRouteToStateSpy.calls.reset() + cvtStateToRouteSpy.calls.reset() + cvtRouteToStateSpy.calls.reset() + mockRouter.navigateByUrl.calls.reset() + mockRouter.navigate.calls.reset() }) describe('> on state set', () => { + const fakeState = { + foo: 'bar' + } + beforeEach(() => { - it('> should call cvtStateToHashedRoutes', fakeAsync(() => { - cvtStateToHashedRoutesSpy.and.callFake(() => ``) - const service = TestBed.inject(RouterService) + cvtRouteToStateSpy.and.resolveTo({ + url: '/', + stateFromRoute: fakeState + }) const store = TestBed.inject(MockStore) - const fakeState = { - foo: 'bar' - } store.setState(fakeState) - tick(320) - expect(cvtStateToHashedRoutesSpy).toHaveBeenCalledWith(fakeState) + const service = TestBed.inject(RouterService) + }) + + it('> should call cvtStateToHashedRoutes', fakeAsync(() => { + mockRouter.events.next( + new NavigationEnd(1, '/', '/') + ) + tick(400) + expect(cvtStateToRouteSpy).toHaveBeenCalledWith(fakeState) })) - it('> if cvtStateToHashedRoutes throws, should navigate to home', fakeAsync(() => { - cvtStateToHashedRoutesSpy.and.callFake(() => { + it('> if cvtStateToRoute throws, should navigate to home', fakeAsync(() => { + cvtStateToRouteSpy.and.callFake(() => { throw new Error(`foo bar`) }) - const service = TestBed.inject(RouterService) - const store = TestBed.inject(MockStore) - const fakeState = { - foo: 'bar' - } - store.setState(fakeState) - tick(320) - location = TestBed.inject(Location) - expect( - location.path() - ).toBe('/') + const baseHref = TestBed.inject(APP_BASE_HREF) + + mockRouter.events.next( + new NavigationEnd(1, '/', '/') + ) + tick(400) + expect(mockRouter.navigate).toHaveBeenCalledWith([baseHref]) })) it('> if cvtStateToHashedRoutes returns, should navigate to expected location', fakeAsync(() => { - cvtStateToHashedRoutesSpy.and.callFake(() => { - return `foo/bar` - }) - const service = TestBed.inject(RouterService) - const store = TestBed.inject(MockStore) - const fakeState = { - foo: 'bar' - } - store.setState(fakeState) - tick(320) - location = TestBed.inject(Location) - expect( - location.path() - ).toBe('/foo/bar') + cvtStateToRouteSpy.and.returnValue(`foo/bar`) + mockRouter.events.next( + new NavigationEnd(1, '/', '/') + ) + tick(400) + expect(mockRouter.navigateByUrl).toHaveBeenCalledWith('/foo/bar') })) describe('> does not excessively call navigateByUrl', () => { - let navigateSpy: jasmine.Spy - let navigateByUrlSpy: jasmine.Spy - beforeEach(() => { - const router = TestBed.inject(Router) - navigateSpy = spyOn(router, 'navigate').and.callThrough() - navigateByUrlSpy = spyOn(router, 'navigateByUrl').and.callThrough() - }) - afterEach(() => { - navigateSpy.calls.reset() - navigateByUrlSpy.calls.reset() - }) it('> navigate calls navigateByUrl', fakeAsync(() => { - cvtStateToHashedRoutesSpy.and.callFake(() => { + cvtStateToRouteSpy.and.callFake(() => { return `foo/bar` }) - TestBed.inject(RouterService) - const store = TestBed.inject(MockStore) - store.setState({ - 'hello': 'world' - }) - tick(320) - expect(cvtStateToHashedRoutesSpy).toHaveBeenCalledTimes(1 + 1) - expect(navigateByUrlSpy).toHaveBeenCalledTimes(1) + mockRouter.events.next( + new NavigationEnd(1, '/', '/') + ) + tick(400) + expect(cvtStateToRouteSpy).toHaveBeenCalledTimes(1 + 1) + expect(mockRouter.navigateByUrl).toHaveBeenCalledTimes(1) })) it('> same state should not navigate', fakeAsync(() => { - cvtStateToHashedRoutesSpy.and.callFake(() => { + cvtStateToRouteSpy.and.callFake(() => { return `foo/bar` }) - - TestBed.inject(RouterService) - const router = TestBed.inject(Router) - router.navigate(['foo', 'bar']) - const store = TestBed.inject(MockStore) - store.setState({ - 'hello': 'world' - }) - tick(320) - expect(cvtStateToHashedRoutesSpy).toHaveBeenCalledTimes(1 + 1) - expect(navigateByUrlSpy).toHaveBeenCalledTimes(1) + mockRouter.events.next( + new NavigationEnd(1, '/', '/') + ) + tick(400) + expect(cvtStateToRouteSpy).toHaveBeenCalledTimes(1 + 1) + expect(mockRouter.navigateByUrl).toHaveBeenCalledTimes(1) })) it('> should handle queryParam gracefully', fakeAsync(() => { const searchParam = new URLSearchParams() const sv = '["precomputed://https://object.cscs.ch/v1/AUTH_08c08f9f119744cbbf77e216988da3eb/imgsvc-46d9d64f-bdac-418e-a41b-b7f805068c64"]' searchParam.set('standaloneVolumes', sv) - cvtStateToHashedRoutesSpy.and.callFake(() => { + cvtStateToRouteSpy.and.callFake(() => { return `foo/bar?${searchParam.toString()}` }) - TestBed.inject(RouterService) - const store = TestBed.inject(MockStore) - - TestBed.inject(RouterService) - const router = TestBed.inject(Router) - router.navigate(['foo', `bar`], { queryParams: { standaloneVolumes: sv }}) - store.setState({ - 'hello': 'world' - }) - tick(320) - expect(cvtStateToHashedRoutesSpy).toHaveBeenCalledTimes(1 + 1) - expect(navigateByUrlSpy).toHaveBeenCalledTimes(1) + mockRouter.events.next( + new NavigationEnd(1, '/', '/') + ) + tick(400) + expect(cvtStateToRouteSpy).toHaveBeenCalledTimes(1 + 1) + expect(mockRouter.navigateByUrl).toHaveBeenCalledTimes(1) })) }) }) describe('> on route change', () => { - describe('> compares new state and previous state', () => { + const fakeState = { + foo: 'bar' + } + beforeEach(fakeAsync(() => { + cvtRouteToStateSpy.and.resolveTo(fakeState) + TestBed.inject(RouterService) + const store = TestBed.inject(MockStore) + store.setState(fakeState) + mockRouter.events.next( + new NavigationEnd(1, '/', '/') + ) + tick(400) + })) - it('> calls cvtFullRouteToState', fakeAsync(() => { + describe('> compares new state and previous state', () => { + it('> calls cvtRouteToState', fakeAsync(() => { const fakeParsedState = { bizz: 'buzz' } - cvtFullRouteToStateSpy.and.callFake(() => fakeParsedState) - cvtStateToHashedRoutesSpy.and.callFake(() => { + cvtRouteToStateSpy.and.resolveTo(fakeParsedState) + cvtStateToRouteSpy.and.callFake(() => { return ['bizz', 'buzz'] }) - router = TestBed.inject(Router) - router.navigate(['foo', 'bar']) - - const service = TestBed.inject(RouterService) - - tick() - - expect(cvtFullRouteToStateSpy).toHaveBeenCalledWith( - router.parseUrl('/foo/bar'), {}, service['logError'] + mockRouter.events.next( + new NavigationEnd(1, '/foo/bar', '/foo/bar') ) - discardPeriodicTasks() + tick(160) + expect(cvtRouteToStateSpy).toHaveBeenCalledWith( + new DefaultUrlSerializer().parse('/foo/bar') + ) })) it('> calls cvtStateToHashedRoutes with current state', fakeAsync(() => { @@ -203,23 +197,20 @@ describe('> router.service.ts', () => { const fakeState = { foo: 'bar' } - cvtFullRouteToStateSpy.and.callFake(() => fakeParsedState) + cvtRouteToStateSpy.and.resolveTo(fakeParsedState) const store = TestBed.inject(MockStore) store.setState(fakeState) - cvtStateToHashedRoutesSpy.and.callFake(() => { + cvtStateToRouteSpy.and.callFake(() => { return ['bizz', 'buzz'] }) - router = TestBed.inject(Router) - router.navigate(['foo', 'bar']) - - TestBed.inject(RouterService) + mockRouter.events.next( + new NavigationEnd(1, '/foo/bar', '/foo/bar') + ) - tick() + tick(160) - expect(cvtStateToHashedRoutesSpy).toHaveBeenCalledWith(fakeState) - - discardPeriodicTasks() + expect(cvtStateToRouteSpy).toHaveBeenCalledWith(fakeState) })) describe('> when cvtStateToHashedRoutes ...', () => { @@ -227,44 +218,42 @@ describe('> router.service.ts', () => { const fakeParsedState = { bizz: 'buzz' } - cvtFullRouteToStateSpy.and.callFake(() => fakeParsedState) - cvtStateToHashedRoutesSpy.and.callFake(() => { + cvtRouteToStateSpy.and.resolveTo(fakeParsedState) + cvtStateToRouteSpy.and.callFake(() => { throw new Error(`fizz buzz`) }) - router = TestBed.inject(Router) - router.navigate(['foo', 'bar']) + + mockRouter.events.next( + new NavigationEnd(1, '/foo/bar', '/foo/bar') + ) - TestBed.inject(RouterService) const store = TestBed.inject(MockStore) const dispatchSpy = spyOn(store, 'dispatch') - tick() + tick(160) expect(dispatchSpy).toHaveBeenCalled() - - discardPeriodicTasks() })) it('> ... returns different value, dispatches', fakeAsync(() => { const fakeParsedState = { bizz: 'buzz' } - cvtFullRouteToStateSpy.and.callFake(() => fakeParsedState) - cvtStateToHashedRoutesSpy.and.callFake(() => { + cvtRouteToStateSpy.and.resolveTo(fakeParsedState) + cvtStateToRouteSpy.and.callFake(() => { return `fizz/buzz` }) - router = TestBed.inject(Router) - router.navigate(['foo', 'bar']) + mockRouter.events.next( + new NavigationEnd(1, '/foo/bar', '/foo/bar') + ) TestBed.inject(RouterService) const store = TestBed.inject(MockStore) const dispatchSpy = spyOn(store, 'dispatch') - tick(320) + tick(160) expect(dispatchSpy).toHaveBeenCalled() - - discardPeriodicTasks() })) describe('> returns the same value', () => { @@ -272,30 +261,31 @@ describe('> router.service.ts', () => { const fakeParsedState = { bizz: 'buzz' } - cvtFullRouteToStateSpy.and.callFake(() => fakeParsedState) - cvtStateToHashedRoutesSpy.and.callFake(() => { + cvtRouteToStateSpy.and.resolveTo(fakeParsedState) + cvtStateToRouteSpy.and.callFake(() => { return `foo/bar` }) - router = TestBed.inject(Router) - router.navigate(['foo', 'bar']) + + mockRouter.events.next( + new NavigationEnd(1, '/foo/bar', '/foo/bar') + ) const service = TestBed.inject(RouterService) const store = TestBed.inject(MockStore) const dispatchSpy = spyOn(store, 'dispatch') - tick(320) + tick(160) expect(dispatchSpy).not.toHaveBeenCalled() - discardPeriodicTasks() })) it('> takes into account of customRoute', fakeAsync(() => { const fakeParsedState = { bizz: 'buzz' } - cvtFullRouteToStateSpy.and.callFake(() => fakeParsedState) - cvtStateToHashedRoutesSpy.and.callFake(() => { + cvtRouteToStateSpy.and.resolveTo(fakeParsedState) + cvtStateToRouteSpy.and.callFake(() => { return `foo/bar` }) @@ -304,8 +294,10 @@ describe('> router.service.ts', () => { 'x-foo': 'hello' }) - router = TestBed.inject(Router) - router.navigate(['foo', 'bar', 'x-foo:hello']) + + mockRouter.events.next( + new NavigationEnd(1, '/foo/bar/x-foo:hello', '/foo/bar/x-foo:hello') + ) const store = TestBed.inject(MockStore) const dispatchSpy = spyOn(store, 'dispatch') @@ -313,8 +305,6 @@ describe('> router.service.ts', () => { tick(320) expect(dispatchSpy).not.toHaveBeenCalled() - - discardPeriodicTasks() })) }) }) @@ -323,11 +313,24 @@ describe('> router.service.ts', () => { describe('> customRoute$', () => { let decodeCustomStateSpy: jasmine.Spy + + const fakeState = { + foo: 'bar' + } + let rService: RouterService beforeEach(() => { + cvtRouteToStateSpy.and.resolveTo({ + url: '/', + stateFromRoute: fakeState + }) + const store = TestBed.inject(MockStore) + store.setState(fakeState) + rService = TestBed.inject(RouterService) decodeCustomStateSpy = jasmine.createSpy('decodeCustomState') spyOnProperty(util, 'decodeCustomState').and.returnValue(decodeCustomStateSpy) - - router = TestBed.inject(Router) + mockRouter.events.next( + new NavigationEnd(0, '/', '/') + ) }) afterEach(() => { @@ -339,10 +342,11 @@ describe('> router.service.ts', () => { 'x-foo': 'bar' } decodeCustomStateSpy.and.returnValue(value) - const rService = TestBed.inject(RouterService) - router.navigate(['foo']) - tick(320) + mockRouter.events.next( + new NavigationEnd(1, '/foo', '/foo') + ) + tick(400) expect(rService.customRoute$).toBeObservable( cold('a', { a: { @@ -350,7 +354,6 @@ describe('> router.service.ts', () => { } }) ) - discardPeriodicTasks() })) it('> merges observable from _customRoutes$', fakeAsync(() => { decodeCustomStateSpy.and.returnValue({}) @@ -374,9 +377,8 @@ describe('> router.service.ts', () => { 'x-foo': 'bar' } decodeCustomStateSpy.and.returnValue(value) - const rService = TestBed.inject(RouterService) rService.setCustomRoute('x-fizz', 'buzz') - tick(320) + tick(400) expect(rService.customRoute$).toBeObservable( cold('(ba)', { @@ -389,15 +391,10 @@ describe('> router.service.ts', () => { } }) ) - discardPeriodicTasks() })) it('> subsequent emits overwrites', fakeAsync(() => { decodeCustomStateSpy.and.returnValue({}) - const rService = TestBed.inject(RouterService) - spyOn(router, 'navigateByUrl').and.callFake((() => { - console.log('navigate by url') - }) as any) const customRouteSpy = jasmine.createSpy('customRouteSpy') rService.customRoute$.subscribe(customRouteSpy) @@ -426,7 +423,6 @@ describe('> router.service.ts', () => { for (const c in expectedCalls) { expect(customRouteSpy).toHaveBeenCalledWith(expectedCalls[c]) } - discardPeriodicTasks() })) }) }) diff --git a/src/routerModule/router.service.ts b/src/routerModule/router.service.ts index a17b554adcc8d430c279d7c241f8f96438ca018d..3359055f7b70b395772c5d27842dffee46b1b374 100644 --- a/src/routerModule/router.service.ts +++ b/src/routerModule/router.service.ts @@ -3,12 +3,14 @@ import { APP_BASE_HREF } from "@angular/common"; import { Inject } from "@angular/core"; import { NavigationEnd, Router } from '@angular/router' import { Store } from "@ngrx/store"; -import { debounceTime, distinctUntilChanged, filter, map, shareReplay, startWith, switchMapTo, take, tap, withLatestFrom } from "rxjs/operators"; -import { generalApplyState } from "src/services/stateStore.helper"; -import { PureContantService } from "src/util"; -import { cvtStateToHashedRoutes, cvtFullRouteToState, encodeCustomState, decodeCustomState, verifyCustomState } from "./util"; -import { BehaviorSubject, combineLatest, merge, Observable, of } from 'rxjs' +import { debounceTime, distinctUntilChanged, filter, map, mapTo, shareReplay, startWith, switchMap, switchMapTo, take, withLatestFrom } from "rxjs/operators"; +import { encodeCustomState, decodeCustomState, verifyCustomState } from "./util"; +import { BehaviorSubject, combineLatest, concat, merge, Observable, timer } from 'rxjs' import { scan } from 'rxjs/operators' +import { RouteStateTransformSvc } from "./routeStateTransform.service"; +import { SAPI } from "src/atlasComponents/sapi"; +import { generalActions } from "src/state"; + @Injectable({ providedIn: 'root' @@ -20,11 +22,9 @@ export class RouterService { console.log(...e) } - private _customRoute$ = new BehaviorSubject<{ - [key: string]: string - }>({}) + private _customRoute$ = new BehaviorSubject<Record<string, string>>({}) - public customRoute$: Observable<Record<string, any>> + public customRoute$: Observable<Record<string, string>> setCustomRoute(key: string, state: string){ if (!verifyCustomState(key)) { @@ -37,7 +37,8 @@ export class RouterService { constructor( router: Router, - pureConstantService: PureContantService, + routeToStateTransformSvc: RouteStateTransformSvc, + sapi: SAPI, store$: Store<any>, @Inject(APP_BASE_HREF) baseHref: string ){ @@ -51,7 +52,31 @@ export class RouterService { navEnd$.subscribe() - const ready$ = pureConstantService.allFetchingReady$.pipe( + /** + * onload + */ + const onload$ = navEnd$.pipe( + take(1), + filter(ev => ev.urlAfterRedirects !== '/'), + switchMap(ev => + routeToStateTransformSvc.cvtRouteToState( + router.parseUrl( + ev.urlAfterRedirects + ) + ) + ) + ) + onload$.subscribe( + state => { + store$.dispatch( + generalActions.generalApplyState({ + state + }) + ) + } + ) + + const ready$ = sapi.atlases$.pipe( filter(flag => !!flag), take(1), shareReplay(1), @@ -91,25 +116,47 @@ export class RouterService { ), ) - ready$.pipe( - switchMapTo( - navEnd$.pipe( - withLatestFrom( - store$, - this.customRoute$.pipe( - startWith({}) - ) + /** + * does work too well =( + */ + concat( + onload$.pipe( + mapTo(false) + ), + timer(160).pipe( + mapTo(false) + ), + ready$.pipe( + map(val => !!val) + ) + ).pipe( + switchMap(() => navEnd$), + map(navEv => navEv.urlAfterRedirects), + switchMap(url => + routeToStateTransformSvc.cvtRouteToState( + router.parseUrl( + url ) + ).then(stateFromRoute => { + return { + url, + stateFromRoute + } + }) + ), + withLatestFrom( + store$, + this.customRoute$.pipe( + startWith({}) ) ) ).subscribe(arg => { - const [ev, state, customRoutes] = arg + const [{ stateFromRoute, url }, currentState, customRoutes] = arg + const fullPath = url - const fullPath = ev.urlAfterRedirects - const stateFromRoute = cvtFullRouteToState(router.parseUrl(fullPath), state, this.logError) let routeFromState: string try { - routeFromState = cvtStateToHashedRoutes(state) + routeFromState = routeToStateTransformSvc.cvtStateToRoute(currentState) } catch (_e) { routeFromState = `` } @@ -119,27 +166,41 @@ export class RouterService { if (!customStatePath) continue routeFromState += `/${customStatePath}` } - if ( fullPath !== `/${routeFromState}`) { store$.dispatch( - generalApplyState({ + generalActions.generalApplyState({ state: stateFromRoute }) ) } }) - // TODO this may still be a bit finiky. - // we rely on that update of store happens within 160ms - // which may or many not be - ready$.pipe( + /** + * wait until onload completes + * wait for 160ms + * then start listening to store changes, and update route accordingly + * + * this is so that initial state can be loaded + */ + concat( + onload$.pipe( + mapTo(false) + ), + timer(160).pipe( + mapTo(false) + ), + ready$.pipe( + map(val => !!val) + ) + ).pipe( + filter(flag => flag), switchMapTo( combineLatest([ store$.pipe( debounceTime(160), map(state => { try { - return cvtStateToHashedRoutes(state) + return routeToStateTransformSvc.cvtStateToRoute(state) } catch (e) { this.logError(e) return `` @@ -160,7 +221,12 @@ export class RouterService { ) ) ).subscribe(routePath => { - if (routePath === '') { + /** + * routePath may be falsy + * or empty string + * both can be caught by !routePath + */ + if (!routePath) { router.navigate([ baseHref ]) } else { diff --git a/src/routerModule/type.ts b/src/routerModule/type.ts index fa8855ad8202348ddbec659db149c84296e49a7c..13205ad3e09e0969a8c419a5d9803b2883bc2de3 100644 --- a/src/routerModule/type.ts +++ b/src/routerModule/type.ts @@ -9,10 +9,6 @@ export type TUrlAtlas<T> = { r?: T // region selected } -export type TUrlPreviewDs<T> = { - dsp: T // dataset preview -} - export type TUrlPlugin<T> = { pl: T // pluginState } @@ -21,10 +17,14 @@ export type TUrlNav<T> = { ['@']: T // navstring } +export type TUrlViewFeat<T> = { + f: T +} + export type TConditional<T> = Partial< - TUrlPreviewDs<T> & TUrlPlugin<T> & - TUrlNav<T> + TUrlNav<T> & + TUrlViewFeat<T> > export type TUrlPathObj<T, V> = diff --git a/src/routerModule/util.spec.ts b/src/routerModule/util.spec.ts index 35e0baefc23aa62bd6531b3ea32a2659744c63e7..95971bb00bf7f5d69249f9392d35def82d80d484 100644 --- a/src/routerModule/util.spec.ts +++ b/src/routerModule/util.spec.ts @@ -1,163 +1,6 @@ -import { TestBed } from '@angular/core/testing' -import { MockStore, provideMockStore } from '@ngrx/store/testing' -import { uiStatePreviewingDatasetFilesSelector } from 'src/services/state/uiState/selectors' -import { viewerStateGetSelectedAtlas, viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector, viewerStateSelectorNavigation, viewerStateSelectorStandaloneVolumes } from 'src/services/state/viewerState/selectors' -import { cvtFullRouteToState, cvtStateToHashedRoutes, DummyCmp, encodeCustomState, routes, verifyCustomState } from './util' -import { encodeNumber } from './cipher' -import { Router } from '@angular/router' -import { RouterTestingModule } from '@angular/router/testing' -import * as parsedRoute from './parseRouteToTmplParcReg' -import { spaceMiscInfoMap } from 'src/util/pureConstant.service' +import { encodeCustomState, verifyCustomState } from './util' describe('> util.ts', () => { - describe('> cvtFullRouteToState', () => { - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - RouterTestingModule.withRoutes(routes, { - useHash: true - }) - ], - declarations: [ - DummyCmp - ] - }) - }) - beforeEach(() => { - }) - it('> should be able to decode region properly', () => { - - }) - - describe('> decode sv', () => { - let sv: any - beforeEach(() => { - const searchParam = new URLSearchParams() - searchParam.set('standaloneVolumes', '["precomputed://https://object.cscs.ch/v1/AUTH_08c08f9f119744cbbf77e216988da3eb/imgsvc-46d9d64f-bdac-418e-a41b-b7f805068c64"]') - const svRoute = `/?${searchParam.toString()}` - const router = TestBed.inject(Router) - const parsedUrl = router.parseUrl(svRoute) - const returnState = cvtFullRouteToState(parsedUrl, {}) - sv = returnState?.viewerState?.standaloneVolumes - }) - - it('> sv should be truthy', () => { - expect(sv).toBeTruthy() - }) - - it('> sv should be array', () => { - expect( - Array.isArray(sv) - ).toBeTrue() - }) - - it('> sv should have length 1', () => { - expect(sv.length).toEqual(1) - }) - - it('> sv[0] should be expected value', () => { - expect(sv[0]).toEqual( - 'precomputed://https://object.cscs.ch/v1/AUTH_08c08f9f119744cbbf77e216988da3eb/imgsvc-46d9d64f-bdac-418e-a41b-b7f805068c64' - ) - }) - }) - - describe('> navigation', () => { - let parseSpy: jasmine.Spy - let mapGetSpy: jasmine.Spy - beforeEach(() => { - parseSpy = spyOnProperty(parsedRoute, 'parseSearchParamForTemplateParcellationRegion') - mapGetSpy = spyOn(spaceMiscInfoMap, 'get') - }) - it('> if not present, should show something palatable', () => { - parseSpy.and.returnValue(() => ({ - parcellationSelected: { - id: 'dummpy-id-parc' - }, - regionsSelected: [], - templateSelected: { - id: 'dummpy-id-tmpl-sel' - }, - })) - - const scale = 0.25 - - mapGetSpy.and.returnValue({ scale }) - - const router = TestBed.inject(Router) - const route = `/a:juelich:iav:atlas:v1.0.0:1/t:minds:core:referencespace:v1.0.0:dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2/p:minds:core:parcellationatlas:v1.0.0:94c1125b-b87e-45e4-901c-00daee7f2579-290` - const parsedUrl = router.parseUrl(route) - const { viewerState = {} } = cvtFullRouteToState(parsedUrl, {}) || {} - const { navigation } = viewerState - const { - orientation, - perspectiveOrientation, - position, - zoom, - perspectiveZoom, - } = navigation - - expect(orientation).toEqual([0,0,0,1]) - expect(perspectiveOrientation).toEqual([ - 0.3140767216682434, - -0.7418519854545593, - 0.4988985061645508, - -0.3195493221282959 - ]) - expect(position).toEqual([0,0,0]) - expect(zoom).toEqual(350000 * scale) - expect(perspectiveZoom).toEqual(1922235.5293810747 * scale) - }) - }) - }) - - describe('> cvtStateToHashedRoutes', () => { - let mockStore: MockStore - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - provideMockStore() - ] - }) - - mockStore = TestBed.inject(MockStore) - - mockStore.overrideSelector(viewerStateGetSelectedAtlas, null) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, null) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, null) - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, []) - mockStore.overrideSelector(viewerStateSelectorStandaloneVolumes, []) - mockStore.overrideSelector(viewerStateSelectorNavigation, null) - - mockStore.overrideSelector(uiStatePreviewingDatasetFilesSelector, []) - }) - describe('> should be able encode region properly', () => { - - it('> regular ngId', () => { - const ngId = 'foobar' - const labelIndex = 124 - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [{ - labelIndex, - ngId - }]) - const s = cvtStateToHashedRoutes({}) - expect(s).toContain(`r:${ngId}::${encodeNumber(labelIndex, { float: false })}`) - }) - - it('> ngId containing ()', () => { - - const ngId = 'foobar(1)' - const labelIndex = 124 - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [{ - labelIndex, - ngId - }]) - const s = cvtStateToHashedRoutes({}) - expect(s).toContain(`r:foobar%25281%2529::${encodeNumber(labelIndex, { float: false })}`) - }) - }) - }) describe('> verifyCustomState', () => { it('> should return false on bad custom state', () => { diff --git a/src/routerModule/util.ts b/src/routerModule/util.ts index 39ec477aebc60ba1bd201971923bc5c3a6f539c5..ee8f181f0431509a7b6512a762df034ec56982ab 100644 --- a/src/routerModule/util.ts +++ b/src/routerModule/util.ts @@ -1,32 +1,17 @@ -import { viewerStateGetSelectedAtlas, viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector, viewerStateSelectorNavigation, viewerStateSelectorStandaloneVolumes } from "src/services/state/viewerState/selectors" -import { encodeNumber, decodeToNumber, separator, encodeURIFull } from './cipher' import { UrlSegment, UrlTree } from "@angular/router" -import { getShader, PMAP_DEFAULT_CONFIG } from "src/util/constants" -import { mixNgLayers } from "src/services/state/ngViewerState.store" -import { PLUGINSTORE_CONSTANTS } from 'src/services/state/pluginState.helper' -import { viewerStateHelperStoreName } from "src/services/state/viewerState.store.helper" -import { uiStatePreviewingDatasetFilesSelector } from "src/services/state/uiState/selectors" import { Component } from "@angular/core" -import { - TUrlStandaloneVolume, - TUrlAtlas, - TUrlPathObj, -} from './type' +export const encodeId = (id: string) => id && id.replace(/\//g, ':') +export const decodeId = (codedId: string) => codedId && codedId.replace(/:/g, '/') -import { - parseSearchParamForTemplateParcellationRegion, - encodeId, -} from './parseRouteToTmplParcReg' -import { spaceMiscInfoMap } from "src/util/pureConstant.service" - -const endcodePath = (key: string, val: string|string[]) => +export const endcodePath = (key: string, val: string|string[]) => key[0] === '?' ? `?${key}=${val}` : `${key}:${Array.isArray(val) ? val.map(v => encodeURI(v)).join('::') : encodeURI(val)}` -const decodePath = (path: string) => { + +export const decodePath = (path: string) => { const re = /^(.*?):(.*?)$/.exec(path) if (!re) return null return { @@ -46,266 +31,6 @@ export const DEFAULT_NAV = { position: [0, 0, 0], } -export const cvtFullRouteToState = (fullPath: UrlTree, state: any, _warnCb?: (arg: string) => void) => { - - const warnCb = _warnCb || ((...e: any[]) => console.warn(...e)) - const pathFragments: UrlSegment[] = fullPath.root.hasChildren() - ? fullPath.root.children['primary'].segments - : [] - - const returnState = JSON.parse(JSON.stringify(state)) - - const returnObj: Partial<TUrlPathObj<string[], unknown>> = {} - for (const f of pathFragments) { - const { key, val } = decodePath(f.path) || {} - if (!key || !val) continue - returnObj[key] = val - } - - // TODO deprecate - // but ensure bkwd compat? - const niftiLayers = fullPath.queryParams['niftiLayers'] - if (niftiLayers) { - const layers = niftiLayers - .split('__') - .map(layer => { - return { - name : layer, - source : `nifti://${layer}`, - mixability : 'nonmixable', - shader : getShader(PMAP_DEFAULT_CONFIG), - } as any - }) - const { ngViewerState } = returnState - ngViewerState.layers = mixNgLayers(ngViewerState.layers, layers) - } - // -- end deprecate - - // logical assignment. Use instead of above after typescript > v4.0.0 - // returnState['viewerState'] ||= {} - if (!returnState['viewerState']) { - returnState['viewerState'] = {} - } - // -- end fix logical assignment - - // nav obj is almost always defined, regardless if standaloneVolume or not - const cViewerState = returnObj['@'] && returnObj['@'][0] - let parsedNavObj: any - if (cViewerState) { - try { - const [ cO, cPO, cPZ, cP, cZ ] = cViewerState.split(`${separator}${separator}`) - const o = cO.split(separator).map(s => decodeToNumber(s, {float: true})) - const po = cPO.split(separator).map(s => decodeToNumber(s, {float: true})) - const pz = decodeToNumber(cPZ) - const p = cP.split(separator).map(s => decodeToNumber(s)) - const z = decodeToNumber(cZ) - parsedNavObj = { - orientation: o, - perspectiveOrientation: po, - perspectiveZoom: pz, - position: p, - zoom: z, - - // flag to allow for animation when enabled - animation: {}, - } - } catch (e) { - /** - * TODO Poisoned encoded char - * send error message - */ - warnCb(`parse nav error`, e) - } - } - - // pluginState should always be defined, regardless if standalone volume or not - const pluginStates = fullPath.queryParams['pl'] - const { pluginState } = returnState - if (pluginStates) { - try { - const arrPluginStates = JSON.parse(pluginStates) - pluginState.initManifests = arrPluginStates.map(url => [PLUGINSTORE_CONSTANTS.INIT_MANIFEST_SRC, url] as [string, string]) - } catch (e) { - /** - * parsing plugin error - */ - warnCb(`parse plugin states error`, e, pluginStates) - } - } - - // preview dataset can and should be displayed regardless of standalone volume or not - - try { - const { uiState } = returnState - const arr = returnObj.dsp - ? [{ - datasetId: returnObj.dsp[0], - filename: returnObj.dsp[1] - }] - : fullPath.queryParams['previewingDatasetFiles'] && JSON.parse(fullPath.queryParams['previewingDatasetFiles']) - if (arr) { - uiState.previewingDatasetFiles = arr.map(({ datasetId, filename }) => { - return { - datasetId, - filename - } - }) - } - } catch (e) { - // parsing previewingDatasetFiles - warnCb(`parse dsp error`, e) - } - - // If sv (standaloneVolume is defined) - // only load sv in state - // ignore all other params - // /#/sv:%5B%22precomputed%3A%2F%2Fhttps%3A%2F%2Fobject.cscs.ch%2Fv1%2FAUTH_08c08f9f119744cbbf77e216988da3eb%2Fimgsvc-46d9d64f-bdac-418e-a41b-b7f805068c64%22%5D - const standaloneVolumes = fullPath.queryParams['standaloneVolumes'] - if (!!standaloneVolumes) { - try { - const parsedArr = JSON.parse(standaloneVolumes) - if (!Array.isArray(parsedArr)) throw new Error(`Parsed standalone volumes not of type array`) - - returnState['viewerState']['standaloneVolumes'] = parsedArr - returnState['viewerState']['navigation'] = parsedNavObj - return returnState - } catch (e) { - // if any error occurs, parse rest per normal - warnCb(`parse standalone volume error`, e) - } - } else { - returnState['viewerState']['standaloneVolumes'] = [] - } - - try { - const { parcellationSelected, regionsSelected, templateSelected } = parseSearchParamForTemplateParcellationRegion(returnObj as TUrlPathObj<string[], TUrlAtlas<string[]>>, fullPath, state, warnCb) - returnState['viewerState']['parcellationSelected'] = parcellationSelected - returnState['viewerState']['regionsSelected'] = regionsSelected - returnState['viewerState']['templateSelected'] = templateSelected - - if (templateSelected) { - const { scale } = spaceMiscInfoMap.get(templateSelected.id) || { scale: 1 } - returnState['viewerState']['navigation'] = parsedNavObj || ({ - ...DEFAULT_NAV, - zoom: 350000 * scale, - perspectiveZoom: 1922235.5293810747 * scale - }) - } - } catch (e) { - // if error, show error on UI? - warnCb(`parse template, parc, region error`, e) - } - - /** - * parsing template to get atlasId - */ - (() => { - - const viewreHelperState = returnState[viewerStateHelperStoreName] || {} - const { templateSelected, parcellationSelected } = returnState['viewerState'] - const { fetchedAtlases, ...rest } = viewreHelperState - - const selectedAtlas = (fetchedAtlases || []).find(a => a['templateSpaces'].find(t => t['@id'] === (templateSelected && templateSelected['@id']))) - - const overlayLayer = selectedAtlas && selectedAtlas['parcellations'].find(p => p['@id'] === (parcellationSelected && parcellationSelected['@id'])) - - viewreHelperState['selectedAtlasId'] = selectedAtlas && selectedAtlas['@id'] - viewreHelperState['overlayingAdditionalParcellations'] = (overlayLayer && !overlayLayer['baseLayer']) - ? [ overlayLayer ] - : [] - })() - - return returnState -} - -export const cvtStateToHashedRoutes = (state): string => { - // TODO check if this causes memleak - const selectedAtlas = viewerStateGetSelectedAtlas(state) - const selectedTemplate = viewerStateSelectedTemplateSelector(state) - const selectedParcellation = viewerStateSelectedParcellationSelector(state) - const selectedRegions = viewerStateSelectedRegionsSelector(state) - const standaloneVolumes = viewerStateSelectorStandaloneVolumes(state) - const navigation = viewerStateSelectorNavigation(state) - - const previewingDatasetFiles = uiStatePreviewingDatasetFilesSelector(state) - let dsPrvString: string - const searchParam = new URLSearchParams() - - if (previewingDatasetFiles && Array.isArray(previewingDatasetFiles)) { - const dsPrvArr = [] - const datasetPreviews = (previewingDatasetFiles as {datasetId: string, filename: string}[]) - for (const preview of datasetPreviews) { - dsPrvArr.push(preview) - } - - if (dsPrvArr.length === 1) { - dsPrvString = `${dsPrvArr[0].datasetId}::${dsPrvArr[0].filename}` - } - } - - let cNavString: string - if (navigation) { - const { orientation, perspectiveOrientation, perspectiveZoom, position, zoom } = navigation - if (orientation && perspectiveOrientation && perspectiveZoom && position && zoom) { - cNavString = [ - orientation.map((n: number) => encodeNumber(n, {float: true})).join(separator), - perspectiveOrientation.map(n => encodeNumber(n, {float: true})).join(separator), - encodeNumber(Math.floor(perspectiveZoom)), - Array.from(position).map((v: number) => Math.floor(v)).map(n => encodeNumber(n)).join(separator), - encodeNumber(Math.floor(zoom)), - ].join(`${separator}${separator}`) - } - } - - // encoding selected regions - let selectedRegionsString - if (selectedRegions.length === 1) { - const region = selectedRegions[0] - const { ngId, labelIndex } = region - selectedRegionsString = `${ngId}::${encodeNumber(labelIndex, { float: false })}` - } - let routes: any - - routes= { - // for atlas - a: selectedAtlas && encodeId(selectedAtlas['@id']), - // for template - t: selectedTemplate && encodeId(selectedTemplate['@id'] || selectedTemplate['fullId']), - // for parcellation - p: selectedParcellation && encodeId(selectedParcellation['@id'] || selectedParcellation['fullId']), - // for regions - r: selectedRegionsString && encodeURIFull(selectedRegionsString), - // nav - ['@']: cNavString, - // dataset file preview - dsp: dsPrvString && encodeURI(dsPrvString), - } as TUrlPathObj<string, TUrlAtlas<string>> - - /** - * if any params needs to overwrite previosu routes, put them here - */ - if (standaloneVolumes && Array.isArray(standaloneVolumes) && standaloneVolumes.length > 0) { - searchParam.set('standaloneVolumes', JSON.stringify(standaloneVolumes)) - routes = { - // nav - ['@']: cNavString, - dsp: dsPrvString && encodeURI(dsPrvString) - } as TUrlPathObj<string|string[], TUrlStandaloneVolume<string[]>> - } - - const routesArr: string[] = [] - for (const key in routes) { - if (!!routes[key]) { - const segStr = endcodePath(key, routes[key]) - routesArr.push(segStr) - } - } - - return searchParam.toString() === '' - ? routesArr.join('/') - : `${routesArr.join('/')}?${searchParam.toString()}` -} - export const verifyCustomState = (key: string) => { return /^x-/.test(key) } diff --git a/src/screenshot/screenshotCmp/screenshot.template.html b/src/screenshot/screenshotCmp/screenshot.template.html index ad7143b67c6c49b75f8341450b70633e997149eb..ee570082170d036ee4429f425ce4f0d112255d79 100644 --- a/src/screenshot/screenshotCmp/screenshot.template.html +++ b/src/screenshot/screenshotCmp/screenshot.template.html @@ -2,7 +2,7 @@ <ng-template #placeholderTmpl> <div class="d-flex align-items-center justify-content-center w-100 h-100 cover"> - <span class="iv-custom-comp text"> + <span class="sxplr-custom-cmp text"> <h2 class="mat-h2 text-center"> <span> Drag a box to take a screenshot or diff --git a/src/services/effect/effect.spec.ts b/src/services/effect/effect.spec.ts deleted file mode 100644 index 906e23daf3f5d76b601306fa10fff91e6c912611..0000000000000000000000000000000000000000 --- a/src/services/effect/effect.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {} from 'jasmine' -import { UseEffects } from './effect' -import { TestBed } from '@angular/core/testing' -import { Observable } from 'rxjs' -import { SELECT_PARCELLATION, SELECT_REGIONS } from '../state/viewerState.store' -import { provideMockActions } from '@ngrx/effects/testing' -import { hot } from 'jasmine-marbles' -import { provideMockStore } from '@ngrx/store/testing' -import { defaultRootState } from '../stateStore.service' -import { viewerStateNewViewer } from '../state/viewerState/actions' - -describe('effect.ts', () => { - - describe('UseEffects', () => { - let actions$:Observable<any> - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - UseEffects, - provideMockActions(() => actions$), - provideMockStore({ initialState: defaultRootState }) - ] - }) - }) - - it('both SELECT_PARCELLATION and viewerStateNewViewer.type actions should trigger onParcellationSelected$', () => { - const useEffectsInstance: UseEffects = TestBed.inject(UseEffects) - actions$ = hot( - 'ab', - { - a: { type: SELECT_PARCELLATION }, - b: { type: viewerStateNewViewer.type } - } - ) - expect( - useEffectsInstance.onParcChange$ - ).toBeObservable( - hot( - 'aa', - { - a: { type: SELECT_REGIONS, selectRegions: [] } - } - ) - ) - }) - }) -}) diff --git a/src/services/effect/effect.ts b/src/services/effect/effect.ts deleted file mode 100644 index 10e5506143f0963ddaea23b81bd3e09509c8623b..0000000000000000000000000000000000000000 --- a/src/services/effect/effect.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { Injectable, OnDestroy } from "@angular/core"; -import { Actions, Effect, ofType } from "@ngrx/effects"; -import { select, Store } from "@ngrx/store"; -import { merge, Observable, Subscription, combineLatest } from "rxjs"; -import { filter, map, shareReplay, switchMap, take, withLatestFrom, mapTo, distinctUntilChanged } from "rxjs/operators"; -import { LoggingService } from "src/logging"; -import { IavRootStoreInterface, recursiveFindRegionWithLabelIndexId } from '../stateStore.service'; -import { viewerStateNewViewer, viewerStateSelectAtlas, viewerStateSetSelectedRegionsWithIds, viewerStateToggleLayer } from "../state/viewerState.store.helper"; -import { deserialiseParcRegionId, serialiseParcellationRegion } from "common/util" -import { getGetRegionFromLabelIndexId } from 'src/util/fn' -import { actionAddToRegionsSelectionWithIds, actionSelectLandmarks, viewerStateSelectParcellation, viewerStateSelectRegionWithIdDeprecated, viewerStateSetSelectedRegions } from "../state/viewerState/actions"; - -@Injectable({ - providedIn: 'root', -}) -export class UseEffects implements OnDestroy { - - @Effect() - setRegionsSelected$: Observable<any> - - constructor( - private actions$: Actions, - private store$: Store<IavRootStoreInterface>, - private log: LoggingService, - ) { - - this.regionsSelected$ = this.store$.pipe( - select('viewerState'), - select('regionsSelected'), - shareReplay(1), - ) - - this.setRegionsSelected$ = combineLatest( - this.actions$.pipe( - ofType(viewerStateSetSelectedRegionsWithIds), - map(action => { - const { selectRegionIds } = action - return selectRegionIds - }) - ), - this.store$.pipe( - select('viewerState'), - select('parcellationSelected'), - filter(v => !!v), - distinctUntilChanged() - ), - ).pipe( - map(([ids, parcellation]) => { - const getRegionFromlabelIndexId = getGetRegionFromLabelIndexId({ parcellation }) - const selectRegions = !!ids && Array.isArray(ids) - ? ids.map(id => getRegionFromlabelIndexId({ labelIndexId: id })).filter(v => !!v) - : [] - /** - * only allow 1 selection at a time - */ - return viewerStateSetSelectedRegions({ - selectRegions: selectRegions.slice(0,1) - }) - }) - ) - - this.onDeselectRegionsWithId$ = this.actions$.pipe( - ofType(ACTION_TYPES.DESELECT_REGIONS_WITH_ID), - map(action => { - const { deselecRegionIds } = action as any - return deselecRegionIds - }), - withLatestFrom(this.regionsSelected$), - map(([ deselecRegionIds, alreadySelectedRegions ]) => { - const deselectSet = new Set(deselecRegionIds) - return viewerStateSetSelectedRegions({ - selectRegions: alreadySelectedRegions - .filter(({ ngId, labelIndex }) => !deselectSet.has(serialiseParcellationRegion({ ngId, labelIndex }))), - }) - }), - ) - - this.addToSelectedRegions$ = this.actions$.pipe( - ofType(actionAddToRegionsSelectionWithIds.type), - map(action => { - const { selectRegionIds } = action - return selectRegionIds - }), - switchMap(selectRegionIds => this.updatedParcellation$.pipe( - filter(p => !!p), - take(1), - map(p => [selectRegionIds, p]), - )), - map(this.convertRegionIdsToRegion), - withLatestFrom(this.regionsSelected$), - map(([ selectedRegions, alreadySelectedRegions ]) => { - return viewerStateSetSelectedRegions({ - selectRegions: this.removeDuplicatedRegions(selectedRegions, alreadySelectedRegions), - }) - }), - ) - } - - private regionsSelected$: Observable<any[]> - - public ngOnDestroy() { - while (this.subscriptions.length > 0) { - this.subscriptions.pop().unsubscribe() - } - } - - private subscriptions: Subscription[] = [] - - private parcellationSelected$ = this.actions$.pipe( - ofType(viewerStateSelectParcellation.type), - ) - - - private updatedParcellation$ = this.store$.pipe( - select('viewerState'), - select('parcellationSelected'), - map(p => p.updated ? p : null), - shareReplay(1), - ) - - @Effect() - public onDeselectRegionsWithId$: Observable<any> - - private convertRegionIdsToRegion = ([selectRegionIds, parcellation]) => { - const { ngId: defaultNgId } = parcellation - return (selectRegionIds as any[]) - .map(labelIndexId => deserialiseParcRegionId(labelIndexId)) - .map(({ ngId, labelIndex }) => { - return { - labelIndexId: serialiseParcellationRegion({ - ngId: ngId || defaultNgId, - labelIndex, - }), - } - }) - .map(({ labelIndexId }) => { - return recursiveFindRegionWithLabelIndexId({ - regions: parcellation.regions, - labelIndexId, - inheritedNgId: defaultNgId, - }) - }) - .filter(v => { - if (!v) { - this.log.log(`SELECT_REGIONS_WITH_ID, some ids cannot be parsed intto label index`) - } - return !!v - }) - } - - private removeDuplicatedRegions = (...args) => { - const set = new Set() - const returnArr = [] - for (const regions of args) { - for (const region of regions) { - if (!set.has(region.name)) { - returnArr.push(region) - set.add(region.name) - } - } - } - return returnArr - } - - @Effect() - public addToSelectedRegions$: Observable<any> - - /** - * for backwards compatibility. - * older versions of atlas viewer may only have labelIndex as region identifier - */ - @Effect() - public onSelectRegionWithId = this.actions$.pipe( - ofType(viewerStateSelectRegionWithIdDeprecated.type), - map(action => { - const { selectRegionIds } = action - return selectRegionIds - }), - switchMap(selectRegionIds => this.updatedParcellation$.pipe( - filter(p => !!p), - take(1), - map(parcellation => [selectRegionIds, parcellation]), - )), - map(this.convertRegionIdsToRegion), - map(selectRegions => { - return viewerStateSetSelectedRegions({ - selectRegions - }) - }), - ) - - /** - * side effect of selecting a parcellation means deselecting all regions - */ - @Effect() - public onParcChange$ = merge( - this.actions$.pipe( - ofType(viewerStateToggleLayer.type) - ), - this.parcellationSelected$, - this.actions$.pipe( - ofType(viewerStateNewViewer.type) - ), - this.actions$.pipe( - ofType(viewerStateSelectAtlas.type) - ) - ).pipe( - mapTo( - viewerStateSetSelectedRegions({ - selectRegions: [] - }) - ) - ) - - /** - * side effects of loading a new template space - * Landmarks will no longer be accurate (differente template space) - */ - - @Effect() - public onNewViewerResetLandmarkSelected$ = this.actions$.pipe( - ofType(viewerStateNewViewer.type), - mapTo( - actionSelectLandmarks({ - landmarks: [] - }) - ) - ) -} - -export const compareRegions: (r1: any, r2: any) => boolean = (r1, r2) => { - if (!r1) { return !r2 } - if (!r2) { return !r1 } - return r1.ngId === r2.ngId - && r1.labelIndex === r2.labelIndex - && r1.name === r2.name -} - -const ACTION_TYPES = { - DESELECT_REGIONS_WITH_ID: 'DESELECT_REGIONS_WITH_ID', -} - -export const VIEWER_STATE_ACTION_TYPES = ACTION_TYPES diff --git a/src/services/effect/newTemplate.effect.ts b/src/services/effect/newTemplate.effect.ts deleted file mode 100644 index f2c2541fe6fee218be482ad7f67b0b84443a8928..0000000000000000000000000000000000000000 --- a/src/services/effect/newTemplate.effect.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Injectable } from "@angular/core"; -import { Actions, Effect, ofType } from "@ngrx/effects"; -import { Observable } from "rxjs"; -import { mapTo } from "rxjs/operators"; -import { DATASETS_ACTIONS_TYPES } from "../state/dataStore.store"; -import { viewerStateNewViewer } from "../state/viewerState/actions"; - -@Injectable({ - providedIn: 'root' -}) - -export class NewTemplateUseEffect{ - - @Effect() - public onNewTemplateShouldClearPreviewDataset$: Observable<any> - - constructor( - private actions$: Actions - ){ - this.onNewTemplateShouldClearPreviewDataset$ = this.actions$.pipe( - ofType(viewerStateNewViewer.type), - mapTo({ - type: DATASETS_ACTIONS_TYPES.CLEAR_PREVIEW_DATASETS - }) - ) - } -} diff --git a/src/services/effect/pluginUseEffect.spec.ts b/src/services/effect/pluginUseEffect.spec.ts deleted file mode 100644 index 978b28ad0485761025ad70b263e0440e26929326..0000000000000000000000000000000000000000 --- a/src/services/effect/pluginUseEffect.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { TestBed } from "@angular/core/testing"; -import { HttpClientModule, HTTP_INTERCEPTORS, HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpHeaders } from "@angular/common/http"; -import { PluginServiceUseEffect } from "./pluginUseEffect"; -import { Observable, of } from "rxjs"; -import { Action } from "@ngrx/store"; -import { provideMockActions } from "@ngrx/effects/testing"; -import { provideMockStore } from "@ngrx/store/testing"; -import { defaultRootState } from "../stateStore.service"; -import { PLUGINSTORE_CONSTANTS, PLUGINSTORE_ACTION_TYPES } from '../state/pluginState.helper' -import { Injectable } from "@angular/core"; -import { getRandomHex } from 'common/util' -import { PluginServices } from "src/plugin"; -import { AngularMaterialModule } from "src/sharedModules"; -import { hot } from "jasmine-marbles"; -import { BS_ENDPOINT } from "src/util/constants"; - -const actions$: Observable<Action> = of({type: 'TEST'}) - -const manifest = { - name: getRandomHex(), - templateURL: 'http://localhost:12345/template.html', - scriptURL: 'http://localhost:12345/script.js' -} - -const template = getRandomHex() -const script = getRandomHex() - -@Injectable() -class HTTPInterceptorClass implements HttpInterceptor{ - intercept(req: HttpRequest<any>, next: HttpHandler):Observable<HttpEvent<any>>{ - if(req.url.indexOf('http://localhost:12345') >= 0) { - if (req.url.indexOf('manifest.json') >= 0) { - - const headers = new HttpHeaders() - headers.set('content-type', 'application/json') - return of(new HttpResponse({ - status: 200, - body: manifest, - headers - })) - } - - if (req.url.indexOf('template.html') >= 0) { - - const headers = new HttpHeaders() - headers.set('content-type', 'text/html') - return of(new HttpResponse({ - status: 200, - body: template, - headers - })) - } - - if (req.url.indexOf('script.js') >= 0) { - - const headers = new HttpHeaders() - headers.set('content-type', 'application/javascript') - return of(new HttpResponse({ - status: 200, - body: script, - headers - })) - } - } - return next.handle(req) - } -} - -@Injectable() -class MockPluginService{ - public launchNewWidget(arg) { - console.log('launch new widget') - } -} - -describe('pluginUseEffect.ts', () => { - - let spy - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - HttpClientModule, - AngularMaterialModule - ], - providers: [ - PluginServiceUseEffect, - provideMockActions(() => actions$), - provideMockStore({ - initialState: { - ...defaultRootState, - pluginState:{ - initManifests: [ - [ PLUGINSTORE_CONSTANTS.INIT_MANIFEST_SRC, 'http://localhost:12345/manifest.json' ] - ] - } - } - }), - { - provide: HTTP_INTERCEPTORS, - useClass: HTTPInterceptorClass, - multi: true - }, - { - provide: PluginServices, - useClass: MockPluginService - }, - { - provide: BS_ENDPOINT, - useValue: `http://localhost:1234` - } - ] - }).compileComponents() - const pluginServices = TestBed.get(PluginServices) - spy = spyOn(pluginServices, 'launchNewWidget') - }) - - it('initManifests should fetch manifest.json', () => { - const effect = TestBed.get(PluginServiceUseEffect) as PluginServiceUseEffect - expect( - effect.initManifests$ - ).toBeObservable( - hot('a', { - a: {type: PLUGINSTORE_ACTION_TYPES.CLEAR_INIT_PLUGIN} - }) - ) - expect(spy).toHaveBeenCalledWith(manifest) - }) -}) diff --git a/src/services/effect/pluginUseEffect.ts b/src/services/effect/pluginUseEffect.ts deleted file mode 100644 index bd0a74772c60d75cceda439cb5402df584c4e2af..0000000000000000000000000000000000000000 --- a/src/services/effect/pluginUseEffect.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Injectable } from "@angular/core" -import { Effect } from "@ngrx/effects" -import { select, Store } from "@ngrx/store" -import { Observable, forkJoin } from "rxjs" -import { filter, map, startWith, switchMap } from "rxjs/operators" -import { PluginServices } from "src/plugin/atlasViewer.pluginService.service" -import { PLUGINSTORE_CONSTANTS, PLUGINSTORE_ACTION_TYPES, pluginStateSelectorInitManifests } from 'src/services/state/pluginState.helper' -import { HttpClient } from "@angular/common/http" -import { getHttpHeader } from "src/util/constants" - -@Injectable({ - providedIn: 'root', -}) - -export class PluginServiceUseEffect { - - @Effect() - public initManifests$: Observable<any> - - constructor( - store$: Store<any>, - pluginService: PluginServices, - http: HttpClient - ) { - this.initManifests$ = store$.pipe( - select(pluginStateSelectorInitManifests), - startWith([]), - map(arr => { - // only launch plugins that has init manifest src label on it - return arr.filter(([ source ]) => source === PLUGINSTORE_CONSTANTS.INIT_MANIFEST_SRC) - }), - filter(arr => arr.length > 0), - switchMap(arr => forkJoin( - arr.map(([_source, url]) => - http.get(url, { - headers: getHttpHeader(), - responseType: 'json' - }) - ) - )), - map((jsons: any[]) => { - for (const json of jsons){ - pluginService.launchNewWidget(json) - } - - // clear init manifest - return { - type: PLUGINSTORE_ACTION_TYPES.CLEAR_INIT_PLUGIN, - } - }), - ) - } -} diff --git a/src/services/localFile.service.ts b/src/services/localFile.service.ts deleted file mode 100644 index 045ac686b54f7b65e2985f7d75c2bce85ce705c6..0000000000000000000000000000000000000000 --- a/src/services/localFile.service.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Injectable } from "@angular/core"; -import { Store } from "@ngrx/store"; -import { SNACKBAR_MESSAGE } from "./state/uiState.store"; -import { IavRootStoreInterface } from "./stateStore.service"; -import { DATASETS_ACTIONS_TYPES } from "./state/dataStore.store"; - -/** - * experimental service handling local user files such as nifti and gifti - */ - -@Injectable({ - providedIn: 'root', -}) - -export class LocalFileService { - public SUPPORTED_EXT = SUPPORTED_EXT - private supportedExtSet = new Set(SUPPORTED_EXT) - - constructor( - private store: Store<IavRootStoreInterface>, - ) { - - } - - private niiUrl: string - - public handleFileDrop(files: File[]) { - try { - this.validateDrop(files) - for (const file of files) { - const ext = this.getExtension(file.name) - switch (ext) { - case NII: { - this.handleNiiFile(file) - break; - } - default: - throw new Error(`File ${file.name} does not have a file handler`) - } - } - } catch (e) { - this.store.dispatch({ - type: SNACKBAR_MESSAGE, - snackbarMessage: `Opening local NIFTI error: ${e.toString()}`, - }) - } - } - - private getExtension(filename: string) { - const match = /(\.\w*?)$/i.exec(filename) - return (match && match[1]) || '' - } - - private validateDrop(files: File[]) { - if (files.length !== 1) { - throw new Error('Interactive atlas viewer currently only supports drag and drop of one file at a time') - } - for (const file of files) { - const ext = this.getExtension(file.name) - if (!this.supportedExtSet.has(ext)) { - throw new Error(`File ${file.name}${ext === '' ? ' ' : (' with extension ' + ext)} cannot be loaded. The supported extensions are: ${this.SUPPORTED_EXT.join(', ')}`) - } - } - } - - private handleNiiFile(file: File) { - - if (this.niiUrl) { - URL.revokeObjectURL(this.niiUrl) - } - this.niiUrl = URL.createObjectURL(file) - - this.store.dispatch({ - type: DATASETS_ACTIONS_TYPES.PREVIEW_DATASET, - payload: { - file: { - mimetype: 'application/json', - url: this.niiUrl - } - } - }) - - this.showLocalWarning() - } - - private showLocalWarning() { - this.store.dispatch({ - type: SNACKBAR_MESSAGE, - snackbarMessage: `Warning: sharing URL will not share the loaded local file`, - }) - } -} - -const NII = `.nii` -const GII = '.gii' - -const SUPPORTED_EXT = [ - NII, - GII, -] diff --git a/src/services/state/dataState/actions.ts b/src/services/state/dataState/actions.ts deleted file mode 100644 index 34af72ff7364bb0c575eb3cbf21aae9a739a9890..0000000000000000000000000000000000000000 --- a/src/services/state/dataState/actions.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createAction, props } from "@ngrx/store"; -import { IKgDataEntry } from "src/databrowser.fallback"; - -export const datastateActionToggleFav = createAction( - `[datastate] toggleFav`, - props<{payload: { fullId: string }}>() -) - -export const datastateActionUpdateFavDataset = createAction( - `[datastate] updateFav`, - props<{ favDataEntries: any[] }>() -) - -export const datastateActionUnfavDataset = createAction( - `[datastate] unFav`, - props<{ payload: { fullId: string } }>() -) - -export const datastateActionFavDataset = createAction( - `[datastate] fav`, - props<{ payload: { fullId: string } }>() -) - -export const datastateActionFetchedDataentries = createAction( - `[datastate] fetchedDatastate`, - props<{ fetchedDataEntries: IKgDataEntry[] }>() -) \ No newline at end of file diff --git a/src/services/state/dataStore.store.ts b/src/services/state/dataStore.store.ts deleted file mode 100644 index 962168dd111b4219e69c6f595f5ccf596df2fc95..0000000000000000000000000000000000000000 --- a/src/services/state/dataStore.store.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * TODO move to databrowser module - */ - -import { Action } from '@ngrx/store' -import { LOCAL_STORAGE_CONST } from 'src/util/constants' -import { datastateActionFetchedDataentries, datastateActionUpdateFavDataset } from './dataState/actions' -import { IHasId } from 'src/util/interfaces' -import { generalApplyState } from '../stateStore.helper' - -/** - * TODO merge with databrowser.usereffect.ts - */ - -export interface DatasetPreview { - datasetId: string - filename: string -} - -export interface IStateInterface { - fetchedDataEntries: IDataEntry[] - favDataEntries: Partial<IDataEntry>[] - fetchedSpatialData: IDataEntry[] -} - -// TODO deprecate -export const defaultState = { - fetchedDataEntries: [], - favDataEntries: (() => { - try { - const saved = localStorage.getItem(LOCAL_STORAGE_CONST.FAV_DATASET) - const arr = JSON.parse(saved) as any[] - return arr.every(item => item && !!item.fullId) - ? arr - : [] - } catch (e) { - // TODO propagate error - return [] - } - })(), - fetchedSpatialData: [], -} - -export const getStateStore = ({ state: state = defaultState } = {}) => (prevState: IStateInterface = state, action: Partial<IActionInterface>) => { - - switch (action.type) { - case datastateActionFetchedDataentries.type: - case FETCHED_DATAENTRIES: { - return { - ...prevState, - fetchedDataEntries : action.fetchedDataEntries, - } - } - case FETCHED_SPATIAL_DATA: { - return { - ...prevState, - fetchedSpatialData : action.fetchedDataEntries, - } - } - case datastateActionUpdateFavDataset.type: { - const { favDataEntries = [] } = action - return { - ...prevState, - favDataEntries, - } - } - case generalApplyState.type: { - const { dataStore } = (action as any).state - return dataStore - } - default: return prevState - } -} - -// must export a named function for aot compilation -// see https://github.com/angular/angular/issues/15587 -// https://github.com/amcdnl/ngrx-actions/issues/23 -// or just google for: -// -// angular function expressions are not supported in decorators - -const defaultStateStore = getStateStore() - -export function stateStore(state, action) { - return defaultStateStore(state, action) -} - -export interface IActionInterface extends Action { - favDataEntries: IDataEntry[] - fetchedDataEntries: IDataEntry[] - fetchedSpatialData: IDataEntry[] - payload?: any -} - -export const FETCHED_DATAENTRIES = 'FETCHED_DATAENTRIES' -export const FETCHED_SPATIAL_DATA = `FETCHED_SPATIAL_DATA` - -// TODO deprecate in favour of src/ui/datamodule/constants.ts - -export interface IActivity { - methods: string[] - preparation: string[] - protocols: string[] -} - -export interface IDataEntry { - activity: IActivity[] - name: string - description: string - license: string[] - licenseInfo: string[] - parcellationRegion: IParcellationRegion[] - formats: string[] - custodians: string[] - contributors: string[] - referenceSpaces: IReferenceSpace[] - files: File[] - publications: IPublication[] - embargoStatus: IHasId[] - - methods: string[] - protocols: string[] - - preview?: boolean - - /** - * TODO typo, should be kgReferences - */ - kgReference: string[] - - id: string - fullId: string -} - -export interface IParcellationRegion { - id?: string - name: string -} - -export interface IReferenceSpace { - name: string -} - -export interface IPublication { - name: string - doi: string - cite: string -} - -export interface IProperty { - description: string - publications: IPublication[] -} - -export interface ILandmark { - type: string // e.g. sEEG recording site, etc - name: string - templateSpace: string // possibily inherited from LandmarkBundle (?) - geometry: IPointLandmarkGeometry | IPlaneLandmarkGeometry | IOtherLandmarkGeometry - properties: IProperty - files: File[] -} - -export interface IDataStateInterface { - fetchedDataEntries: IDataEntry[] - - /** - * Map that maps parcellation name to a Map, which maps datasetname to Property Object - */ - fetchedMetadataMap: Map<string, Map<string, {properties: IProperty}>> -} - -export interface IPointLandmarkGeometry extends ILandmarkGeometry { - position: [number, number, number] -} - -export interface IPlaneLandmarkGeometry extends ILandmarkGeometry { - // corners have to be CW or CCW (no zigzag) - corners: [[number, number, number], [number, number, number], [number, number, number], [number, number, number]] -} - -export interface IOtherLandmarkGeometry extends ILandmarkGeometry { - vertices: Array<[number, number, number]> - meshIdx: Array<[number, number, number]> -} - -interface ILandmarkGeometry { - type: 'point' | 'plane' - space?: 'voxel' | 'real' -} - -export interface IFile { - name: string - absolutePath: string - byteSize: number - contentType: string -} - -export interface ViewerPreviewFile { - name: string - filename: string - mimetype: string - referenceSpaces: { - name: string - fullId: string - }[] - url?: string - data?: any - position?: any -} - -export interface IFileSupplementData { - data: any -} - -const ACTION_TYPES = { - PREVIEW_DATASET: 'PREVIEW_DATASET', - CLEAR_PREVIEW_DATASET: 'CLEAR_PREVIEW_DATASET', - CLEAR_PREVIEW_DATASETS: 'CLEAR_PREVIEW_DATASETS' -} - -export const DATASETS_ACTIONS_TYPES = ACTION_TYPES diff --git a/src/services/state/ngViewerState.store.helper.ts b/src/services/state/ngViewerState.store.helper.ts deleted file mode 100644 index 6f23c74d1c306a3b74e824fb69c7adc0743d0cc8..0000000000000000000000000000000000000000 --- a/src/services/state/ngViewerState.store.helper.ts +++ /dev/null @@ -1,22 +0,0 @@ -// TODO to be merged with ng viewer state after refactor -export { INgLayerInterface, PANELS } from './ngViewerState/constants' - -export { - ngViewerActionAddNgLayer, - ngViewerActionRemoveNgLayer, - ngViewerActionSetPerspOctantRemoval, - ngViewerActionToggleMax, - ngViewerActionClearView, - ngViewerActionSetPanelOrder, - ngViewerActionForceShowSegment, -} from './ngViewerState/actions' - -export { - ngViewerSelectorClearView, - ngViewerSelectorClearViewEntries, - ngViewerSelectorNehubaReady, - ngViewerSelectorOctantRemoval, - ngViewerSelectorPanelMode, - ngViewerSelectorPanelOrder, - ngViewerSelectorLayers, -} from './ngViewerState/selectors' diff --git a/src/services/state/ngViewerState.store.spec.ts b/src/services/state/ngViewerState.store.spec.ts deleted file mode 100644 index 21173833cf134a9e53274c3017abf97bdef947fb..0000000000000000000000000000000000000000 --- a/src/services/state/ngViewerState.store.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing" -import { TestBed } from "@angular/core/testing" -import { provideMockActions } from "@ngrx/effects/testing" -import { Action } from "@ngrx/store" -import { provideMockStore } from "@ngrx/store/testing" -import { Observable, of } from "rxjs" -import { PureContantService } from "src/util" -import { generalApplyState } from "../stateStore.helper" -import { NgViewerUseEffect } from "./ngViewerState.store" - -const action$: Observable<Action> = of({ type: 'TEST'}) -const initState = {} -describe('> ngViewerState.store.ts', () => { - describe('> NgViewerUseEffect', () => { - let ef: NgViewerUseEffect - beforeEach(() => { - - TestBed.configureTestingModule({ - imports: [ - HttpClientTestingModule, - ], - providers: [ - provideMockActions(() => action$), - provideMockStore({ initialState: initState }), - { - provide: PureContantService, - useValue: { - useTouchUI$: of(false), - backendUrl: `http://localhost:3000/` - } - } - ] - }) - }) - - it('> shoudl be insable', () => { - ef = TestBed.inject(NgViewerUseEffect) - expect(ef).toBeTruthy() - }) - - describe('> applySavedUserConfig$', () => { - - let ctrl: HttpTestingController - beforeEach(() => { - ctrl = TestBed.inject(HttpTestingController) - ef = TestBed.inject(NgViewerUseEffect) - }) - - afterEach(() => { - ctrl.verify() - }) - - it('> if http response errors, user$ should be stream of null', () => { - - ef.applySavedUserConfig$.subscribe(_action => { - // should not emit - expect(false).toEqual(true) - }) - const resp1 = ctrl.expectOne('http://localhost:3000/user/config') - resp1.error(null, { - status: 404, - }) - }) - - it('> if http response contains truthy error key, user should return stream of null', () => { - - ef.applySavedUserConfig$.subscribe(_action => { - // should not emit - expect(false).toEqual(true) - }) - const resp = ctrl.expectOne('http://localhost:3000/user/config') - resp.flush({ - error: true - }) - }) - - it('> if http response does not contain error key, should return the resp', () => { - - const mUserState = { - foo: 'baz', - baz: 'pineablle' - } - ef.applySavedUserConfig$.subscribe(action => { - expect(action).toEqual(generalApplyState({ - state: { - ...initState, - ngViewerState: { - ...(initState['ngViewerState'] || {}), - ...mUserState, - } - } - })) - }) - const resp = ctrl.expectOne('http://localhost:3000/user/config') - resp.flush({ ngViewerState: mUserState}) - - }) - }) - }) -}) diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts deleted file mode 100644 index 5b1758d60906076840930cafbf878ab834e7e88d..0000000000000000000000000000000000000000 --- a/src/services/state/ngViewerState.store.ts +++ /dev/null @@ -1,407 +0,0 @@ -import { Injectable, OnDestroy } from '@angular/core'; -import { Observable, combineLatest, fromEvent, Subscription, of } from 'rxjs'; -import { Effect, Actions, ofType } from '@ngrx/effects'; -import { withLatestFrom, map, distinctUntilChanged, scan, shareReplay, filter, mapTo, debounceTime, catchError, skip, throttleTime } from 'rxjs/operators'; -import { getNgIds } from 'src/util/fn'; -import { Action, select, Store, createReducer, on } from '@ngrx/store' -import { CYCLE_PANEL_MESSAGE } from 'src/util/constants'; -import { HttpClient } from '@angular/common/http'; -import { INgLayerInterface, ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerActionSetPerspOctantRemoval } from './ngViewerState.store.helper' -import { PureContantService } from 'src/util'; -import { PANELS } from './ngViewerState.store.helper' -import { ngViewerActionToggleMax, ngViewerActionClearView, ngViewerActionSetPanelOrder, ngViewerActionSwitchPanelMode, ngViewerActionForceShowSegment, ngViewerActionNehubaReady, ngViewerActionCycleViews } from './ngViewerState/actions'; -import { generalApplyState } from '../stateStore.helper'; -import { ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from './ngViewerState/selectors'; -import { uiActionSnackbarMessage } from './uiState/actions'; -import { TUserRouteError } from 'src/auth/auth.service'; -import { viewerStateSelectedTemplateSelector } from './viewerState.store.helper'; - -export function mixNgLayers(oldLayers: INgLayerInterface[], newLayers: INgLayerInterface|INgLayerInterface[]): INgLayerInterface[] { - if (newLayers instanceof Array) { - return oldLayers.concat(newLayers) - } else { - return oldLayers.concat({ - ...newLayers, - }) - } -} - -export interface StateInterface { - layers: INgLayerInterface[] - forceShowSegment: boolean | null - nehubaReady: boolean - panelMode: string - panelOrder: string - - octantRemoval: boolean - showSubstrate: boolean - showZoomlevel: boolean - - clearViewQueue: { - [key: string]: boolean - } -} - -export interface ActionInterface extends Action { - layer: INgLayerInterface - layers: INgLayerInterface[] - forceShowSegment: boolean - nehubaReady: boolean - payload: any -} - -export const defaultState: StateInterface = { - layers: [], - forceShowSegment: null, - nehubaReady: false, - panelMode: PANELS.FOUR_PANEL, - panelOrder: `0123`, - - octantRemoval: true, - showSubstrate: null, - showZoomlevel: null, - - clearViewQueue: {} -} - -export const ngViewerStateReducer = createReducer( - defaultState, - on(ngViewerActionClearView, (state, { payload }) => { - const { clearViewQueue } = state - const clearViewQueueUpdated = {...clearViewQueue} - for (const key in payload) { - clearViewQueueUpdated[key] = payload[key] - } - return { - ...state, - clearViewQueue: clearViewQueueUpdated - } - }), - on(ngViewerActionSetPerspOctantRemoval, (state, { octantRemovalFlag }) => { - return { - ...state, - octantRemoval: octantRemovalFlag - } - }), - on(ngViewerActionAddNgLayer, (state, { layer }) => { - return { - ...state, - layers: mixNgLayers(state.layers, layer) - } - }), - on(ngViewerActionSetPanelOrder, (state, { payload }) => { - const { panelOrder } = payload - return { - ...state, - panelOrder - } - }), - on(ngViewerActionSwitchPanelMode, (state, { payload }) => { - const { panelMode } = payload - if (SUPPORTED_PANEL_MODES.indexOf(panelMode as any) < 0) { return state } - return { - ...state, - panelMode - } - }), - on(ngViewerActionRemoveNgLayer, (state, { layer }) => { - - const newLayers = Array.isArray(layer) - ? (() => { - const layerNameSet = new Set(layer.map(l => l.name)) - return state.layers.filter(l => !layerNameSet.has(l.name)) - })() - : state.layers.filter(l => l.name !== layer.name) - return { - ...state, - layers: newLayers - } - }), - on(ngViewerActionForceShowSegment, (state, { forceShowSegment }) => { - return { - ...state, - forceShowSegment - } - }), - on(ngViewerActionNehubaReady, (state, { nehubaReady }) => { - return { - ...state, - nehubaReady - } - }), - on(generalApplyState, (_, { state }) => { - const { ngViewerState } = state - return ngViewerState - }) -) - - -// must export a named function for aot compilation -// see https://github.com/angular/angular/issues/15587 -// https://github.com/amcdnl/ngrx-actions/issues/23 -// or just google for: -// -// angular function expressions are not supported in decorators - -export function stateStore(state, action) { - return ngViewerStateReducer(state, action) -} - -type TUserConfig = { - -} - -type TUserConfigResp = TUserConfig & TUserRouteError - -@Injectable({ - providedIn: 'root', -}) - -export class NgViewerUseEffect implements OnDestroy { - @Effect() - public toggleMaximiseMode$: Observable<any> - - @Effect() - public unmaximiseOrder$: Observable<any> - - @Effect() - public maximiseOrder$: Observable<any> - - @Effect() - public toggleMaximiseCycleMessage$: Observable<any> - - @Effect() - public cycleViews$: Observable<any> - - @Effect() - public removeAllNonBaseLayers$: Observable<any> - - private panelOrder$: Observable<string> - private panelMode$: Observable<string> - - private subscriptions: Subscription[] = [] - - @Effect() - public applySavedUserConfig$: Observable<any> - - constructor( - private actions: Actions, - private store$: Store<any>, - private pureConstantService: PureContantService, - private http: HttpClient, - ){ - - this.applySavedUserConfig$ = this.http.get<TUserConfigResp>(`${this.pureConstantService.backendUrl}user/config`).pipe( - map(json => { - if (json.error) { - throw new Error(json.message || 'User not loggedin.') - } - return json - }), - catchError((err,caught) => of(null)), - filter(v => !!v), - withLatestFrom(this.store$), - map(([{ ngViewerState: fetchedNgViewerState }, state]) => { - const { ngViewerState } = state - return generalApplyState({ - state: { - ...state, - ngViewerState: { - ...ngViewerState, - ...fetchedNgViewerState - }} - }) - }) - ) - - const toggleMaxmimise$ = this.actions.pipe( - ofType(ngViewerActionToggleMax.type), - shareReplay(1), - ) - - this.panelOrder$ = this.store$.pipe( - select(ngViewerSelectorPanelOrder), - distinctUntilChanged(), - ) - - this.panelMode$ = this.store$.pipe( - select(ngViewerSelectorPanelMode), - distinctUntilChanged(), - ) - - this.cycleViews$ = this.actions.pipe( - ofType(ngViewerActionCycleViews.type), - withLatestFrom(this.panelOrder$), - map(([_, panelOrder]) => { - return ngViewerActionSetPanelOrder({ - payload: { - panelOrder: [...panelOrder.slice(1), ...panelOrder.slice(0, 1)].join(''), - } - }) - }), - ) - - this.maximiseOrder$ = toggleMaxmimise$.pipe( - withLatestFrom( - combineLatest([ - this.panelOrder$, - this.panelMode$, - ]), - ), - filter(([_action, [_panelOrder, panelMode]]) => panelMode !== PANELS.SINGLE_PANEL), - map(([ action, [ oldPanelOrder ] ]) => { - const { payload } = action as ActionInterface - const { index = 0 } = payload - - const panelOrder = [...oldPanelOrder.slice(index), ...oldPanelOrder.slice(0, index)].join('') - return ngViewerActionSetPanelOrder({ - payload: { panelOrder }, - }) - }), - ) - - this.unmaximiseOrder$ = toggleMaxmimise$.pipe( - withLatestFrom( - combineLatest([ - this.panelOrder$, - this.panelMode$, - ]), - ), - scan((acc, curr) => { - const [action, [panelOrders, panelMode]] = curr - return [{ - action, - panelOrders, - panelMode, - }, ...acc.slice(0, 1)] - }, [] as any[]), - filter(([ { panelMode } ]) => panelMode === PANELS.SINGLE_PANEL), - map(arr => { - const { - action, - panelOrders, - } = arr[0] - - const { - panelOrders: panelOrdersPrev = null, - } = arr[1] || {} - - const { payload } = action as ActionInterface - const { index = 0 } = payload - - const panelOrder = panelOrdersPrev || [...panelOrders.slice(index), ...panelOrders.slice(0, index)].join('') - - return ngViewerActionSetPanelOrder({ - payload: { panelOrder } - }) - }), - ) - - const scanFn = (acc: string[], curr: string): string[] => [curr, ...acc.slice(0, 1)] - - this.toggleMaximiseMode$ = toggleMaxmimise$.pipe( - withLatestFrom(this.panelMode$.pipe( - scan(scanFn, []), - )), - map(([ _, panelModes ]) => { - return ngViewerActionSwitchPanelMode({ - payload: { - panelMode: panelModes[0] === PANELS.SINGLE_PANEL - ? (panelModes[1] || PANELS.FOUR_PANEL) - : PANELS.SINGLE_PANEL, - }, - }) - }), - ) - - this.toggleMaximiseCycleMessage$ = combineLatest([ - this.toggleMaximiseMode$, - this.pureConstantService.useTouchUI$, - ]).pipe( - filter(([_, useMobileUI]) => !useMobileUI), - map(([toggleMaximiseMode, _]) => toggleMaximiseMode), - filter(({ payload }) => payload.panelMode && payload.panelMode === PANELS.SINGLE_PANEL), - mapTo(uiActionSnackbarMessage({ - snackbarMessage: CYCLE_PANEL_MESSAGE - })), - ) - - /** - * simplify with layer browser - */ - const baseNgLayerName$ = this.store$.pipe( - select(viewerStateSelectedTemplateSelector), - - map(templateSelected => { - if (!templateSelected) { return [] } - - const { ngId , otherNgIds = []} = templateSelected - - return [ - ngId, - ...otherNgIds, - ...templateSelected.parcellations.reduce((acc, curr) => { - return acc.concat([ - curr.ngId, - ...getNgIds(curr.regions), - ]) - }, []), - ] - }), - /** - * get unique array - */ - map(nonUniqueArray => Array.from(new Set(nonUniqueArray))), - /** - * remove falsy values - */ - map(arr => arr.filter(v => !!v)), - ) - - const allLoadedNgLayers$ = this.store$.pipe( - select('viewerState'), - select('loadedNgLayers'), - ) - - this.removeAllNonBaseLayers$ = this.actions.pipe( - ofType(ACTION_TYPES.REMOVE_ALL_NONBASE_LAYERS), - withLatestFrom( - combineLatest( - baseNgLayerName$, - allLoadedNgLayers$, - ), - ), - map(([_, [baseNgLayerNames, loadedNgLayers] ]) => { - const baseNameSet = new Set(baseNgLayerNames) - return loadedNgLayers.filter(l => !baseNameSet.has(l.name)) - }), - map(layer => { - return ngViewerActionRemoveNgLayer({ - layer - }) - }), - ) - } - - public ngOnDestroy() { - while (this.subscriptions.length > 0) { - this.subscriptions.pop().unsubscribe() - } - } -} - -export { INgLayerInterface } - -const ACTION_TYPES = { - - REMOVE_ALL_NONBASE_LAYERS: `REMOVE_ALL_NONBASE_LAYERS`, -} - -export const SUPPORTED_PANEL_MODES = [ - PANELS.FOUR_PANEL, - PANELS.H_ONE_THREE, - PANELS.V_ONE_THREE, - PANELS.SINGLE_PANEL, -] - -export const NG_VIEWER_ACTION_TYPES = ACTION_TYPES diff --git a/src/services/state/ngViewerState/actions.ts b/src/services/state/ngViewerState/actions.ts deleted file mode 100644 index c504a085a33cb3a8c67291a3a5c28da57a687838..0000000000000000000000000000000000000000 --- a/src/services/state/ngViewerState/actions.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { createAction, props, createReducer } from "@ngrx/store" -import { INgLayerInterface } from './constants' - -export const ngViewerActionAddNgLayer = createAction( - '[ngLayerAction] addNgLayer', - props<{ layer: INgLayerInterface|INgLayerInterface[] }>() -) - -export const ngViewerActionRemoveNgLayer = createAction( - '[ngLayerAction] removeNgLayer', - props<{ layer: Partial<INgLayerInterface>|Partial<INgLayerInterface>[] }>() -) - -export const ngViewerActionSetPerspOctantRemoval = createAction( - `[ngViewerAction] setPerspectiveOctant`, - props<{ octantRemovalFlag: boolean }>() -) - -export const ngViewerActionToggleMax = createAction( - `[ngViewerAction] toggleMax`, - props<{ payload: { index: number } }>() -) - -export const ngViewerActionSetPanelOrder = createAction( - `[ngViewerAction] setPanelOrder`, - props<{ payload: { panelOrder: string } }>() -) - -export const ngViewerActionSwitchPanelMode = createAction( - `[ngViewerAction] switchPanelMode`, - props<{ payload: { panelMode: string } }>() -) - -export const ngViewerActionForceShowSegment = createAction( - `[ngViewerAction] forceShowSegment`, - props<{ forceShowSegment: boolean }>() -) - -export const ngViewerActionNehubaReady = createAction( - `[ngViewerAction] nehubaReady`, - props<{ nehubaReady: boolean }>() -) - -/** - * Clear viewer view from additional layers such as PMap or connectivity - * To request view to be cleared, call - * this.store$.dispatch( - * ngViewerActionClearView({ - * payload: { - * ['my-unique-id']: true - * } - * }) - * ) - * - * When finished, call - * - * this.store$.dispatch( - * ngViewerActionClearView({ - * payload: { - * ['my-unique-id']: false - * } - * }) - * ) - */ -export const ngViewerActionClearView = createAction( - `[ngViewerAction] clearView`, - props<{ payload: { [key: string]: boolean }}>() -) - -export const ngViewerActionCycleViews = createAction( - `[ngViewerAction] cycleView` -) \ No newline at end of file diff --git a/src/services/state/ngViewerState/constants.ts b/src/services/state/ngViewerState/constants.ts deleted file mode 100644 index 3e93ae4d0ec03ceec73175d1cba8254d6b4b894b..0000000000000000000000000000000000000000 --- a/src/services/state/ngViewerState/constants.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface INgLayerInterface { - name: string // displayName - source: string - mixability?: string // base | mixable | nonmixable - annotation?: string // - id?: string // unique identifier - visible?: boolean - shader?: string - transform?: any - opacity?: number -} - -export enum PANELS { - FOUR_PANEL = 'FOUR_PANEL', - V_ONE_THREE = 'V_ONE_THREE', - H_ONE_THREE = 'H_ONE_THREE', - SINGLE_PANEL = 'SINGLE_PANEL', -} diff --git a/src/services/state/ngViewerState/selectors.spec.ts b/src/services/state/ngViewerState/selectors.spec.ts deleted file mode 100644 index c44f676f86802ee790f4b7e291e7e79c50ccb849..0000000000000000000000000000000000000000 --- a/src/services/state/ngViewerState/selectors.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ngViewerSelectorClearViewEntries } from './selectors' - -let clearViewQueue = {} - -describe('> ngViewerState/selectors.ts', () => { - describe('> ngViewerSelectorClearViewEntries', () => { - beforeEach(() => { - clearViewQueue = {} - }) - describe('> when prop is not provided', () => { - it('> if clearViewQueue is empty (on startup)', () => { - const result = ngViewerSelectorClearViewEntries.projector(clearViewQueue) - expect(result).toEqual([]) - }) - it('> if clearViewQueue is non empty, but falsy, should return false', () => { - clearViewQueue['hello - world'] = null - clearViewQueue['oo bar'] = false - const result = ngViewerSelectorClearViewEntries.projector(clearViewQueue) - expect(result).toEqual([]) - }) - it('> if clearViewQueue is non empty and truthy, should return true', () => { - clearViewQueue['hello - world'] = 1 - const result = ngViewerSelectorClearViewEntries.projector(clearViewQueue) - expect(result).toEqual(['hello - world']) - }) - }) - }) -}) \ No newline at end of file diff --git a/src/services/state/ngViewerState/selectors.ts b/src/services/state/ngViewerState/selectors.ts deleted file mode 100644 index 7222296d4627a32cf20716d05e2ea9e81d898e2e..0000000000000000000000000000000000000000 --- a/src/services/state/ngViewerState/selectors.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createSelector } from "@ngrx/store"; - -export const ngViewerSelectorClearViewEntries = createSelector( - (state: any) => state?.ngViewerState?.clearViewQueue, - (clearViewQueue = {}) => { - const returnKeys = [] - for (const key in clearViewQueue) { - if (!!clearViewQueue[key]) returnKeys.push(key) - } - return returnKeys - } -) - -export const ngViewerSelectorClearView = createSelector( - ngViewerSelectorClearViewEntries, - keys => keys.length > 0 -) - -export const ngViewerSelectorPanelOrder = createSelector( - state => state['ngViewerState'], - ngViewerState => ngViewerState.panelOrder -) - -export const ngViewerSelectorPanelMode = createSelector( - state => state['ngViewerState'], - ngViewerState => ngViewerState.panelMode -) - -export const ngViewerSelectorOctantRemoval = createSelector( - state => state['ngViewerState'], - ngViewerState => ngViewerState.octantRemoval -) - -export const ngViewerSelectorNehubaReady = createSelector( - state => state['ngViewerState'], - ngViewerState => ngViewerState.nehubaReady -) - -export const ngViewerSelectorLayers = createSelector( - state => state['ngViewerState'], - ngViewerState => ngViewerState?.layers || [] -) \ No newline at end of file diff --git a/src/services/state/pluginState.helper.ts b/src/services/state/pluginState.helper.ts deleted file mode 100644 index e1c48c1941ab716f1bacc6c8980f62c2367a933a..0000000000000000000000000000000000000000 --- a/src/services/state/pluginState.helper.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createSelector } from "@ngrx/store" - -export const PLUGINSTORE_ACTION_TYPES = { - SET_INIT_PLUGIN: `SET_INIT_PLUGIN`, - CLEAR_INIT_PLUGIN: 'CLEAR_INIT_PLUGIN', -} - -export const pluginStateSelectorInitManifests = createSelector( - state => state['pluginState'], - pluginState => pluginState.initManifests -) - -export const PLUGINSTORE_CONSTANTS = { - INIT_MANIFEST_SRC: 'INIT_MANIFEST_SRC', -} diff --git a/src/services/state/pluginState.store.ts b/src/services/state/pluginState.store.ts deleted file mode 100644 index 85bfa20a915ec8ab2e11497321cd66d95edabb91..0000000000000000000000000000000000000000 --- a/src/services/state/pluginState.store.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Action } from '@ngrx/store' -import { generalApplyState } from '../stateStore.helper' -import { PLUGINSTORE_ACTION_TYPES, PLUGINSTORE_CONSTANTS } from './pluginState.helper' -export const defaultState: StateInterface = { - initManifests: [] -} - -export interface StateInterface { - initManifests: Array<[ string, string|null ]> -} - -export interface ActionInterface extends Action { - manifest: { - name: string - initManifestUrl?: string - } -} - - -export const getStateStore = ({ state = defaultState } = {}) => (prevState: StateInterface = state, action: ActionInterface): StateInterface => { - switch (action.type) { - case PLUGINSTORE_ACTION_TYPES.SET_INIT_PLUGIN: { - const newMap = new Map(prevState.initManifests ) - - // reserved source label for init manifest - if (action.manifest.name !== PLUGINSTORE_CONSTANTS.INIT_MANIFEST_SRC) { newMap.set(action.manifest.name, action.manifest.initManifestUrl) } - return { - ...prevState, - initManifests: Array.from(newMap), - } - } - case PLUGINSTORE_ACTION_TYPES.CLEAR_INIT_PLUGIN: { - const { initManifests } = prevState - const newManifests = initManifests.filter(([source]) => source !== PLUGINSTORE_CONSTANTS.INIT_MANIFEST_SRC) - return { - ...prevState, - initManifests: newManifests, - } - } - case generalApplyState.type: { - const { pluginState } = (action as any).state - return pluginState - } - default: return prevState - } -} - -// must export a named function for aot compilation -// see https://github.com/angular/angular/issues/15587 -// https://github.com/amcdnl/ngrx-actions/issues/23 -// or just google for: -// -// angular function expressions are not supported in decorators - -const defaultStateStore = getStateStore() - -export function stateStore(state, action) { - return defaultStateStore(state, action) -} diff --git a/src/services/state/uiState.store.helper.ts b/src/services/state/uiState.store.helper.ts deleted file mode 100644 index 6f1c187c15b94170fcdc5db6b5ba3d0664d6da75..0000000000000000000000000000000000000000 --- a/src/services/state/uiState.store.helper.ts +++ /dev/null @@ -1,37 +0,0 @@ -// TODO merge with uiState.store.ts after refactor completes - -export { - uiActionSetPreviewingDatasetFiles, - uiActionShowSidePanelConnectivity, - uiStateCloseSidePanel, - uiStateCollapseSidePanel, - uiStateExpandSidePanel, - uiStateOpenSidePanel, - uiStateShowBottomSheet, - uiActionSnackbarMessage, - uiActionMouseoverLandmark, - uiActionMouseoverSegments, -} from './uiState/actions' - -export { - uiStatePreviewingDatasetFilesSelector, - uiStateMouseOverSegmentsSelector, - uiStateMouseoverUserLandmark, -} from './uiState/selectors' - -export enum EnumWidgetTypes{ - DATASET_PREVIEW, -} - -export interface IDatasetPreviewData{ - datasetId: string - filename: string - datasetSchema?: string -} - -export type TypeOpenedWidget = { - type: EnumWidgetTypes - data: IDatasetPreviewData -} - -export const SHOW_KG_TOS = `SHOW_KG_TOS` \ No newline at end of file diff --git a/src/services/state/uiState.store.ts b/src/services/state/uiState.store.ts deleted file mode 100644 index cab51af1edb88fdbcf360fb5e5a3628e9809dc51..0000000000000000000000000000000000000000 --- a/src/services/state/uiState.store.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { Injectable, TemplateRef, OnDestroy } from '@angular/core'; -import { Action, select, Store } from '@ngrx/store' - -import { Effect, Actions, ofType } from "@ngrx/effects"; -import { Observable, Subscription } from "rxjs"; -import { filter, map, mapTo, scan, startWith, take } from "rxjs/operators"; -import { COOKIE_VERSION, KG_TOS_VERSION, LOCAL_STORAGE_CONST } from 'src/util/constants' -import { IavRootStoreInterface, GENERAL_ACTION_TYPES } from '../stateStore.service' -import { MatBottomSheetRef, MatBottomSheet } from '@angular/material/bottom-sheet'; -import { uiStateCloseSidePanel, uiStateOpenSidePanel, uiStateCollapseSidePanel, uiStateExpandSidePanel, uiActionSetPreviewingDatasetFiles, uiStateShowBottomSheet, uiActionShowSidePanelConnectivity } from './uiState.store.helper'; -import { viewerStateMouseOverCustomLandmark } from './viewerState/actions'; -import { IUiState } from './uiState/common' -import { uiActionMouseoverLandmark, uiActionMouseoverSegments, uiActionSnackbarMessage } from './uiState/actions'; -export const defaultState: IUiState = { - - previewingDatasetFiles: [], - - mouseOverSegments: [], - mouseOverSegment: null, - - mouseOverLandmark: null, - mouseOverUserLandmark: null, - - focusedSidePanel: null, - sidePanelIsOpen: false, - sidePanelExploreCurrentViewIsOpen: false, - - snackbarMessage: null, - - /** - * replace with server side logic (?) - */ - agreedCookies: localStorage.getItem(LOCAL_STORAGE_CONST.AGREE_COOKIE) === COOKIE_VERSION, - agreedKgTos: localStorage.getItem(LOCAL_STORAGE_CONST.AGREE_KG_TOS) === KG_TOS_VERSION, -} - -export { IUiState } - -export const getStateStore = ({ state = defaultState } = {}) => (prevState: IUiState = state, action: ActionInterface) => { - switch (action.type) { - - case uiActionSetPreviewingDatasetFiles.type: { - const { previewingDatasetFiles } = action as any - return { - ...prevState, - previewingDatasetFiles - } - } - case uiActionMouseoverSegments.type: { - const { segments } = action - return { - ...prevState, - mouseOverSegments: segments, - } - } - case MOUSE_OVER_SEGMENT: - return { - ...prevState, - mouseOverSegment : action.segment, - } - case viewerStateMouseOverCustomLandmark.type: { - const { payload = {} } = action - const { userLandmark: mouseOverUserLandmark = null } = payload - return { - ...prevState, - mouseOverUserLandmark, - } - } - case uiActionMouseoverLandmark.type: - return { - ...prevState, - mouseOverLandmark : action.landmark, - } - case uiActionSnackbarMessage.type: - case SNACKBAR_MESSAGE: { - const { snackbarMessage } = action - /** - * Need to use symbol here, or repeated snackbarMessage will not trigger new event - */ - return { - ...prevState, - snackbarMessage: Symbol(snackbarMessage), - } - } - case uiStateOpenSidePanel.type: - case OPEN_SIDE_PANEL: - return { - ...prevState, - sidePanelIsOpen: true, - } - case uiStateCloseSidePanel.type: - case CLOSE_SIDE_PANEL: - return { - ...prevState, - sidePanelIsOpen: false, - } - case uiActionShowSidePanelConnectivity.type: - case uiStateExpandSidePanel.type: - case EXPAND_SIDE_PANEL_CURRENT_VIEW: - return { - ...prevState, - sidePanelExploreCurrentViewIsOpen: true, - } - case uiStateCollapseSidePanel.type: - case COLLAPSE_SIDE_PANEL_CURRENT_VIEW: - return { - ...prevState, - sidePanelExploreCurrentViewIsOpen: false, - } - - case AGREE_COOKIE: { - /** - * TODO replace with server side logic - */ - localStorage.setItem(LOCAL_STORAGE_CONST.AGREE_COOKIE, COOKIE_VERSION) - return { - ...prevState, - agreedCookies: true, - } - } - case AGREE_KG_TOS: { - /** - * TODO replace with server side logic - */ - localStorage.setItem(LOCAL_STORAGE_CONST.AGREE_KG_TOS, KG_TOS_VERSION) - return { - ...prevState, - agreedKgTos: true, - } - } - case GENERAL_ACTION_TYPES.APPLY_STATE: { - const { uiState } = (action as any).state - return uiState - } - default: return prevState - } -} - -// must export a named function for aot compilation -// see https://github.com/angular/angular/issues/15587 -// https://github.com/amcdnl/ngrx-actions/issues/23 -// or just google for: -// -// angular function expressions are not supported in decorators - -const defaultStateStore = getStateStore() - -export function stateStore(state, action) { - return defaultStateStore(state, action) -} - -export interface ActionInterface extends Action { - segment: any | number - landmark: any - focusedSidePanel?: string - segments?: Array<{ - layer: { - name: string - } - segment: any | null - }> - snackbarMessage: string - - bottomSheetTemplate: TemplateRef<any> - - payload: any -} - -@Injectable({ - providedIn: 'root', -}) - -export class UiStateUseEffect implements OnDestroy{ - - private subscriptions: Subscription[] = [] - - private numRegionSelectedWithHistory$: Observable<any[]> - - @Effect() - public sidePanelOpen$: Observable<any> - - @Effect() - public viewCurrentOpen$: Observable<any> - - private bottomSheetRef: MatBottomSheetRef - - constructor( - store$: Store<IavRootStoreInterface>, - actions$: Actions, - bottomSheet: MatBottomSheet - ) { - this.numRegionSelectedWithHistory$ = store$.pipe( - select('viewerState'), - select('regionsSelected'), - map(arr => arr.length), - startWith(0), - scan((acc, curr) => [curr, ...acc], []), - ) - - this.sidePanelOpen$ = this.numRegionSelectedWithHistory$.pipe( - filter(([curr, prev]) => prev === 0 && curr > 0), - mapTo({ - type: OPEN_SIDE_PANEL, - }), - ) - - this.viewCurrentOpen$ = this.numRegionSelectedWithHistory$.pipe( - filter(([curr, prev]) => prev === 0 && curr > 0), - mapTo({ - type: EXPAND_SIDE_PANEL_CURRENT_VIEW, - }), - ) - - this.subscriptions.push( - actions$.pipe( - ofType(uiStateShowBottomSheet.type) - ).subscribe(({ bottomSheetTemplate, config }) => { - if (!bottomSheetTemplate) { - if (this.bottomSheetRef) { - this.bottomSheetRef.dismiss() - this.bottomSheetRef = null - } - } else { - this.bottomSheetRef = bottomSheet.open(bottomSheetTemplate, config) - this.bottomSheetRef.afterDismissed().subscribe(() => { - this.bottomSheetRef = null - }) - } - }) - ) - } - - ngOnDestroy(){ - while(this.subscriptions.length > 0) { - this.subscriptions.pop().unsubscribe() - } - } -} - -export const MOUSE_OVER_SEGMENT = `MOUSE_OVER_SEGMENT` - -export const CLOSE_SIDE_PANEL = `CLOSE_SIDE_PANEL` -export const OPEN_SIDE_PANEL = `OPEN_SIDE_PANEL` -export const COLLAPSE_SIDE_PANEL_CURRENT_VIEW = `COLLAPSE_SIDE_PANEL_CURRENT_VIEW` -export const EXPAND_SIDE_PANEL_CURRENT_VIEW = `EXPAND_SIDE_PANEL_CURRENT_VIEW` - -export const AGREE_COOKIE = `AGREE_COOKIE` -export const AGREE_KG_TOS = `AGREE_KG_TOS` - -export const SNACKBAR_MESSAGE = uiActionSnackbarMessage.type -export const SHOW_BOTTOM_SHEET = `SHOW_BOTTOM_SHEET` diff --git a/src/services/state/uiState/actions.ts b/src/services/state/uiState/actions.ts deleted file mode 100644 index ae2fbc3bd9535428de9c3912a967ea71ee298c2c..0000000000000000000000000000000000000000 --- a/src/services/state/uiState/actions.ts +++ /dev/null @@ -1,49 +0,0 @@ - -import { createAction, props } from '@ngrx/store' -import { TemplateRef } from '@angular/core' -import { MatBottomSheetConfig } from '@angular/material/bottom-sheet' - -export const uiStateCloseSidePanel = createAction( - '[uiState] closeSidePanel' -) - -export const uiStateOpenSidePanel = createAction( - '[uiState] openSidePanel' -) - -export const uiStateCollapseSidePanel = createAction( - '[uiState] collapseSidePanelCurrentView' -) - -export const uiStateExpandSidePanel = createAction( - '[uiState] expandSidePanelCurrentView' -) - -export const uiStateShowBottomSheet = createAction( - '[uiState] showBottomSheet', - props<{ bottomSheetTemplate: TemplateRef<unknown>, config?: MatBottomSheetConfig }>() -) - -export const uiActionMouseoverLandmark = createAction( - `[uiState] mouseoverLandmark`, - props<{ landmark: string }>() -) - -export const uiActionMouseoverSegments = createAction( - `[uiState] mouseoverSegments`, - props<{ segments: any[] }>() -) - -export const uiActionSetPreviewingDatasetFiles = createAction( - `[uiState] setDatasetPreviews`, - props<{previewingDatasetFiles: {datasetId: string, filename: string}[]}>() -) - -export const uiActionShowSidePanelConnectivity = createAction( - `[uiState] showSidePanelConnectivity` -) - -export const uiActionSnackbarMessage = createAction( - `[uiState] snackbarMessage`, - props<{snackbarMessage: string}>() -) \ No newline at end of file diff --git a/src/services/state/uiState/common.ts b/src/services/state/uiState/common.ts deleted file mode 100644 index dd5da3140f280c9cef5c7cd29637c2577f47587f..0000000000000000000000000000000000000000 --- a/src/services/state/uiState/common.ts +++ /dev/null @@ -1,24 +0,0 @@ -export interface IUiState{ - - previewingDatasetFiles: {datasetId: string, filename: string}[] - - mouseOverSegments: Array<{ - layer: { - name: string - } - segment: any | null - }> - sidePanelIsOpen: boolean - sidePanelExploreCurrentViewIsOpen: boolean - mouseOverSegment: any | number - - mouseOverLandmark: string - mouseOverUserLandmark: any - - focusedSidePanel: string | null - - snackbarMessage: symbol - - agreedCookies: boolean - agreedKgTos: boolean -} diff --git a/src/services/state/uiState/selectors.spec.ts b/src/services/state/uiState/selectors.spec.ts deleted file mode 100644 index 2464c9c8a1a4770a1e1c14afd28cb7c3dd4a3833..0000000000000000000000000000000000000000 --- a/src/services/state/uiState/selectors.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { uiStateMouseOverSegmentsSelector } from './selectors' - -describe('> uiState/selectors.ts', () => { - describe('> mouseOverSegments', () => { - - }) -}) diff --git a/src/services/state/uiState/selectors.ts b/src/services/state/uiState/selectors.ts deleted file mode 100644 index 483539b2d784f567564bc2963233b8dd91dabcb1..0000000000000000000000000000000000000000 --- a/src/services/state/uiState/selectors.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createSelector } from "@ngrx/store"; -import { TMouseOverSegment } from "src/mouseoverModule/type"; -import { IUiState } from './common' - -export const uiStatePreviewingDatasetFilesSelector = createSelector( - state => state['uiState'], - (uiState: IUiState) => uiState['previewingDatasetFiles'] -) - -export const uiStateMouseOverSegmentsSelector = createSelector( - state => state['uiState'], - uiState => uiState['mouseOverSegments'] as TMouseOverSegment[] -) - -export const uiStateMouseOverLandmarkSelector = createSelector( - state => state['uiState'], - uiState => uiState['mouseOverLandmark'] as string -) - -export const uiStateMouseoverUserLandmark = createSelector( - state => state['uiState'], - uiState => uiState['mouseOverUserLandmark'] -) diff --git a/src/services/state/uiState/ui.effects.ts b/src/services/state/uiState/ui.effects.ts deleted file mode 100644 index 128655ad53c9ec87a3655dc4da19f336ab4f918a..0000000000000000000000000000000000000000 --- a/src/services/state/uiState/ui.effects.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Injectable, OnDestroy } from "@angular/core"; -import { MatSnackBar } from "@angular/material/snack-bar"; -import { Actions, ofType } from "@ngrx/effects"; -import { Subscription } from "rxjs"; -import { generalActionError } from "src/services/stateStore.helper"; - -@Injectable({ - providedIn: 'root' -}) - -export class UiEffects implements OnDestroy{ - - private subscriptions: Subscription[] = [] - - constructor( - private actions$: Actions, - snackBar: MatSnackBar - ){ - this.subscriptions.push( - this.actions$.pipe( - ofType(generalActionError.type) - ).subscribe((payload: any) => { - if (!payload.message) console.log(payload) - snackBar.open(payload.message || `Error: cannot complete your action.`, 'Dismiss', { duration: 5000 }) - }) - ) - } - - ngOnDestroy(){ - while (this.subscriptions.length > 0) this.subscriptions.pop().unsubscribe() - } -} diff --git a/src/services/state/userConfigState.helper.spec.ts b/src/services/state/userConfigState.helper.spec.ts deleted file mode 100644 index e007700e80d5b030b8d2d70cb41e71d64dfcd136..0000000000000000000000000000000000000000 --- a/src/services/state/userConfigState.helper.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { selectorPluginCspPermission } from "./userConfigState.helper" - -describe('> userConfigState.helper.ts', () => { - describe('> selectorPluginCspPermission', () => { - const expectedTrue = { - value: true - } - const expectedFalse = { - value: false - } - describe('> malformed init value', () => { - describe('> undefined userconfigstate', () => { - it('> return expected false val', () => { - const returnVal = selectorPluginCspPermission.projector(null, { key: 'foo-bar' }) - expect(returnVal).toEqual(expectedFalse) - }) - }) - describe('> undefined pluginCsp property', () => { - it('> return expected false val', () => { - const returnVal = selectorPluginCspPermission.projector({}, { key: 'foo-bar' }) - expect(returnVal).toEqual(expectedFalse) - }) - }) - }) - - describe('> well fored init valu', () => { - - describe('> undefined key', () => { - it('> return expected false val', () => { - const returnVal = selectorPluginCspPermission.projector({ - pluginCsp: {'yes-man': true} - }, { key: 'foo-bar' }) - expect(returnVal).toEqual(expectedFalse) - }) - }) - - describe('> truthly defined key', () => { - it('> return expected true val', () => { - const returnVal = selectorPluginCspPermission.projector({ pluginCsp: - { 'foo-bar': true } - }, { key: 'foo-bar' }) - expect(returnVal).toEqual(expectedTrue) - }) - }) - }) - }) -}) \ No newline at end of file diff --git a/src/services/state/userConfigState.helper.ts b/src/services/state/userConfigState.helper.ts deleted file mode 100644 index e71be024c1947ccf0fc3f3aca909cebbceeaf046..0000000000000000000000000000000000000000 --- a/src/services/state/userConfigState.helper.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createSelector } from "@ngrx/store" - -export const selectorPluginCspPermission = createSelector( - (state: any) => state.userConfigState, - (userConfigState: any, props: any = {}) => { - const { key } = props as { key: string } - return { - value: !!userConfigState?.pluginCsp?.[key] - } - } -) diff --git a/src/services/state/userConfigState.store.spec.ts b/src/services/state/userConfigState.store.spec.ts deleted file mode 100644 index 4f1b078d5298d3850bdc07148314234eee216889..0000000000000000000000000000000000000000 --- a/src/services/state/userConfigState.store.spec.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing" -import { fakeAsync, TestBed, tick } from "@angular/core/testing" -import { provideMockActions } from "@ngrx/effects/testing" -import { Action } from "@ngrx/store" -import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { from, Observable } from "rxjs" -import { AngularMaterialModule } from "src/sharedModules" -import { PureContantService } from "src/util" -import { DialogService } from "../dialogService.service" -import { actionUpdatePluginCsp, UserConfigStateUseEffect } from "./userConfigState.store" -import { viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "./viewerState/selectors" - -describe('> userConfigState.store.spec.ts', () => { - describe('> UserConfigStateUseEffect', () => { - let action$: Observable<Action> - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - HttpClientTestingModule, - AngularMaterialModule, - ], - providers: [ - provideMockActions(() => action$), - provideMockStore({ - initialState: { - viewerConfigState: { - gpuLimit: 1e9, - animation: true - } - } - }), - DialogService, - { - provide: PureContantService, - useValue: { - backendUrl: 'http://localhost:3000/' - } - } - ] - }) - - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, null) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, null) - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, []) - }) - - it('> can be init', () => { - const useEffect = TestBed.inject(UserConfigStateUseEffect) - expect(useEffect).toBeTruthy() - }) - describe('> setInitPluginPermission$', () => { - let mockHttp: HttpTestingController - let useEffect: UserConfigStateUseEffect - const mockpluginPer = { - 'foo-bar': { - 'script-src': [ - '1', - '2', - ] - } - } - beforeEach(() => { - mockHttp = TestBed.inject(HttpTestingController) - useEffect = TestBed.inject(UserConfigStateUseEffect) - }) - afterEach(() => { - mockHttp.verify() - }) - it('> calls /GET user/pluginPermissions', fakeAsync(() => { - let val - useEffect.setInitPluginPermission$.subscribe(v => val = v) - tick(20) - const req = mockHttp.expectOne(`http://localhost:3000/user/pluginPermissions`) - req.flush(mockpluginPer) - expect(val).toEqual(actionUpdatePluginCsp({ payload: mockpluginPer })) - })) - - it('> if get fn fails', fakeAsync(() => { - let val - useEffect.setInitPluginPermission$.subscribe(v => val = v) - const req = mockHttp.expectOne(`http://localhost:3000/user/pluginPermissions`) - req.error(null, { status: 500, statusText: 'Internal Error' }) - expect(val).toEqual(actionUpdatePluginCsp({ payload: {} })) - })) - }) - }) -}) \ No newline at end of file diff --git a/src/services/state/userConfigState.store.ts b/src/services/state/userConfigState.store.ts deleted file mode 100644 index 81fef3205c4020651d6169012d43c19da67ec0f9..0000000000000000000000000000000000000000 --- a/src/services/state/userConfigState.store.ts +++ /dev/null @@ -1,423 +0,0 @@ -import { Injectable, OnDestroy } from "@angular/core"; -import { Actions, Effect, ofType } from "@ngrx/effects"; -import { Action, createAction, createReducer, props, select, Store, on, createSelector } from "@ngrx/store"; -import { combineLatest, from, Observable, of, Subscription } from "rxjs"; -import { catchError, distinctUntilChanged, filter, map, mapTo, share, shareReplay, switchMap, take, withLatestFrom } from "rxjs/operators"; -import { BACKENDURL, LOCAL_STORAGE_CONST } from "src/util//constants"; -import { DialogService } from "../dialogService.service"; -import { recursiveFindRegionWithLabelIndexId } from "src/util/fn"; -import { serialiseParcellationRegion } from 'common/util' -// Get around the problem of importing duplicated string (ACTION_TYPES), even using ES6 alias seems to trip up the compiler -// TODO file bug and reverse -import { HttpClient } from "@angular/common/http"; -import { actionSetMobileUi, viewerStateNewViewer, viewerStateSelectParcellation, viewerStateSetSelectedRegions } from "./viewerState/actions"; -import { viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "./viewerState/selectors"; -import { PureContantService } from "src/util"; - -interface ICsp{ - 'connect-src'?: string[] - 'script-src'?: string[] -} - -export interface StateInterface { - savedRegionsSelection: RegionSelection[] - /** - * plugin csp - currently store in localStorage - * if user log in, store in user profile - */ - pluginCsp: { - /** - * key === plugin version id - */ - [key: string]: ICsp - } -} - -export interface RegionSelection { - templateSelected: any - parcellationSelected: any - regionsSelected: any[] - name: string - id: string -} - -/** - * for serialisation into local storage/database - */ -interface SimpleRegionSelection { - id: string - name: string - tName: string - pName: string - rSelected: string[] -} - -interface UserConfigAction extends Action { - config?: Partial<StateInterface> - payload?: any -} - -export const defaultState: StateInterface = { - savedRegionsSelection: [], - pluginCsp: {} -} - -export const actionUpdateRegionSelections = createAction( - `[userConfig] updateRegionSelections`, - props<{ config: { savedRegionsSelection: RegionSelection[]} }>() -) - -export const selectorAllPluginsCspPermission = createSelector( - (state: any) => state.userConfigState, - userConfigState => userConfigState.pluginCsp -) - -export const actionUpdatePluginCsp = createAction( - `[userConfig] updatePluginCspPermission`, - props<{ - payload: { - [key: string]: ICsp - } - }>() -) - -export const ACTION_TYPES = { - UPDATE_REGIONS_SELECTIONS: actionUpdateRegionSelections.type, - UPDATE_REGIONS_SELECTION: 'UPDATE_REGIONS_SELECTION', - SAVE_REGIONS_SELECTION: `SAVE_REGIONS_SELECTIONN`, - DELETE_REGIONS_SELECTION: 'DELETE_REGIONS_SELECTION', - - LOAD_REGIONS_SELECTION: 'LOAD_REGIONS_SELECTION', -} - - -export const userConfigReducer = createReducer( - defaultState, - on(actionUpdateRegionSelections, (state, { config }) => { - const { savedRegionsSelection } = config - return { - ...state, - savedRegionsSelection - } - }), - on(actionUpdatePluginCsp, (state, { payload }) => { - return { - ...state, - pluginCsp: payload - } - }) -) - -@Injectable({ - providedIn: 'root', -}) -export class UserConfigStateUseEffect implements OnDestroy { - - private subscriptions: Subscription[] = [] - - constructor( - private actions$: Actions, - private store$: Store<any>, - private dialogService: DialogService, - private http: HttpClient, - private constantSvc: PureContantService, - ) { - const viewerState$ = this.store$.pipe( - select('viewerState'), - shareReplay(1), - ) - - this.parcellationSelected$ = this.store$.pipe( - select(viewerStateSelectedParcellationSelector), - distinctUntilChanged(), - ) - - this.tprSelected$ = combineLatest( - this.store$.pipe( - select(viewerStateSelectedTemplateSelector), - distinctUntilChanged(), - ), - this.parcellationSelected$, - this.store$.pipe( - select(viewerStateSelectedRegionsSelector) - /** - * TODO - * distinct selectedRegions - */ - ), - ).pipe( - map(([ templateSelected, parcellationSelected, regionsSelected ]) => { - return { - templateSelected, parcellationSelected, regionsSelected, - } - }), - ) - - this.savedRegionsSelections$ = this.store$.pipe( - select('userConfigState'), - select('savedRegionsSelection'), - shareReplay(1), - ) - - this.onSaveRegionsSelection$ = this.actions$.pipe( - ofType(ACTION_TYPES.SAVE_REGIONS_SELECTION), - withLatestFrom(this.tprSelected$), - withLatestFrom(this.savedRegionsSelections$), - - map(([[action, tprSelected], savedRegionsSelection]) => { - const { payload = {} } = action as UserConfigAction - const { name = 'Untitled' } = payload - - const { templateSelected, parcellationSelected, regionsSelected } = tprSelected - const newSavedRegionSelection: RegionSelection = { - id: Date.now().toString(), - name, - templateSelected, - parcellationSelected, - regionsSelected, - } - return { - type: ACTION_TYPES.UPDATE_REGIONS_SELECTIONS, - config: { - savedRegionsSelection: savedRegionsSelection.concat([newSavedRegionSelection]), - }, - } as UserConfigAction - }), - ) - - this.onDeleteRegionsSelection$ = this.actions$.pipe( - ofType(ACTION_TYPES.DELETE_REGIONS_SELECTION), - withLatestFrom(this.savedRegionsSelections$), - map(([ action, savedRegionsSelection ]) => { - const { payload = {} } = action as UserConfigAction - const { id } = payload - return { - type: ACTION_TYPES.UPDATE_REGIONS_SELECTIONS, - config: { - savedRegionsSelection: savedRegionsSelection.filter(srs => srs.id !== id), - }, - } - }), - ) - - this.onUpdateRegionsSelection$ = this.actions$.pipe( - ofType(ACTION_TYPES.UPDATE_REGIONS_SELECTION), - withLatestFrom(this.savedRegionsSelections$), - map(([ action, savedRegionsSelection]) => { - const { payload = {} } = action as UserConfigAction - const { id, ...rest } = payload - return { - type: ACTION_TYPES.UPDATE_REGIONS_SELECTIONS, - config: { - savedRegionsSelection: savedRegionsSelection - .map(srs => srs.id === id - ? { ...srs, ...rest } - : { ...srs }), - }, - } - }), - ) - - this.subscriptions.push( - this.actions$.pipe( - ofType(ACTION_TYPES.LOAD_REGIONS_SELECTION), - map(action => { - const { payload = {}} = action as UserConfigAction - const { savedRegionsSelection }: {savedRegionsSelection: RegionSelection} = payload - return savedRegionsSelection - }), - filter(val => !!val), - withLatestFrom(this.tprSelected$), - switchMap(([savedRegionsSelection, { parcellationSelected, templateSelected, regionsSelected }]) => - from(this.dialogService.getUserConfirm({ - title: `Load region selection: ${savedRegionsSelection.name}`, - message: `This action would cause the viewer to navigate away from the current view. Proceed?`, - })).pipe( - catchError((e, obs) => of(null)), - map(() => { - return { - savedRegionsSelection, - parcellationSelected, - templateSelected, - regionsSelected, - } - }), - filter(val => !!val), - ), - ), - switchMap(({ savedRegionsSelection, parcellationSelected, templateSelected, regionsSelected }) => { - if (templateSelected.name !== savedRegionsSelection.templateSelected.name ) { - /** - * template different, dispatch viewerStateNewViewer.type - */ - this.store$.dispatch( - viewerStateNewViewer({ - selectParcellation: savedRegionsSelection.parcellationSelected, - selectTemplate: savedRegionsSelection.templateSelected, - }) - ) - return this.parcellationSelected$.pipe( - filter(p => p.updated), - take(1), - map(() => { - return { - regionsSelected: savedRegionsSelection.regionsSelected, - } - }), - ) - } - - if (parcellationSelected.name !== savedRegionsSelection.parcellationSelected.name) { - /** - * parcellation different, dispatch SELECT_PARCELLATION - */ - this.store$.dispatch( - viewerStateSelectParcellation({ - selectParcellation: savedRegionsSelection.parcellationSelected, - }) - ) - return this.parcellationSelected$.pipe( - filter(p => p.updated), - take(1), - map(() => { - return { - regionsSelected: savedRegionsSelection.regionsSelected, - } - }), - ) - } - - return of({ - regionsSelected: savedRegionsSelection.regionsSelected, - }) - }), - ).subscribe(({ regionsSelected }) => { - this.store$.dispatch( - viewerStateSetSelectedRegions({ - selectRegions: regionsSelected, - }) - ) - }), - ) - - this.subscriptions.push( - this.store$.pipe( - select('viewerConfigState'), - ).subscribe(({ gpuLimit, animation }) => { - - if (gpuLimit) { - window.localStorage.setItem(LOCAL_STORAGE_CONST.GPU_LIMIT, gpuLimit.toString()) - } - if (typeof animation !== 'undefined' && animation !== null) { - window.localStorage.setItem(LOCAL_STORAGE_CONST.ANIMATION, animation.toString()) - } - }), - ) - - this.subscriptions.push( - this.actions$.pipe( - ofType(actionSetMobileUi.type), - map((action: any) => { - const { payload } = action - const { useMobileUI } = payload - return useMobileUI - }), - filter(bool => bool !== null), - ).subscribe((bool: boolean) => { - window.localStorage.setItem(LOCAL_STORAGE_CONST.MOBILE_UI, JSON.stringify(bool)) - }), - ) - - this.subscriptions.push( - this.actions$.pipe( - ofType(ACTION_TYPES.UPDATE_REGIONS_SELECTIONS), - ).subscribe(action => { - const { config = {} } = action as UserConfigAction - const { savedRegionsSelection } = config - const simpleSRSs = savedRegionsSelection.map(({ id, name, templateSelected, parcellationSelected, regionsSelected }) => { - return { - id, - name, - tName: templateSelected.name, - pName: parcellationSelected.name, - rSelected: regionsSelected.map(({ ngId, labelIndex }) => serialiseParcellationRegion({ ngId, labelIndex })), - } as SimpleRegionSelection - }) - - /** - * TODO save server side on per user basis - */ - window.localStorage.setItem(LOCAL_STORAGE_CONST.SAVED_REGION_SELECTIONS, JSON.stringify(simpleSRSs)) - }), - ) - - const savedSRSsString = window.localStorage.getItem(LOCAL_STORAGE_CONST.SAVED_REGION_SELECTIONS) - const savedSRSs: SimpleRegionSelection[] = savedSRSsString && JSON.parse(savedSRSsString) - - this.restoreSRSsFromStorage$ = viewerState$.pipe( - filter(() => !!savedSRSs), - select('fetchedTemplates'), - distinctUntilChanged(), - map(fetchedTemplates => savedSRSs.map(({ id, name, tName, pName, rSelected }) => { - const templateSelected = fetchedTemplates.find(t => t.name === tName) - const parcellationSelected = templateSelected && templateSelected.parcellations.find(p => p.name === pName) - const regionsSelected = parcellationSelected && rSelected.map(labelIndexId => recursiveFindRegionWithLabelIndexId({ - regions: parcellationSelected.regions, - labelIndexId, - inheritedNgId: parcellationSelected.ngId - })) - return { - templateSelected, - parcellationSelected, - id, - name, - regionsSelected, - } as RegionSelection - })), - filter(restoredSavedRegions => restoredSavedRegions.every(rs => rs.regionsSelected && rs.regionsSelected.every(r => !!r))), - take(1), - map(savedRegionsSelection => { - return { - type: ACTION_TYPES.UPDATE_REGIONS_SELECTIONS, - config: { savedRegionsSelection }, - } - }), - ) - } - - public ngOnDestroy() { - while (this.subscriptions.length > 0) { - this.subscriptions.pop().unsubscribe() - } - } - - /** - * Temmplate Parcellation Regions selected - */ - private tprSelected$: Observable<{templateSelected: any, parcellationSelected: any, regionsSelected: any[]}> - private savedRegionsSelections$: Observable<any[]> - private parcellationSelected$: Observable<any> - - @Effect() - public onSaveRegionsSelection$: Observable<any> - - @Effect() - public onDeleteRegionsSelection$: Observable<any> - - @Effect() - public onUpdateRegionsSelection$: Observable<any> - - @Effect() - public restoreSRSsFromStorage$: Observable<any> - - @Effect() - public setInitPluginPermission$ = this.http.get(`${this.constantSvc.backendUrl}user/pluginPermissions`, { - responseType: 'json' - }).pipe( - /** - * TODO show warning? - */ - catchError(() => of({})), - map((json: any) => actionUpdatePluginCsp({ payload: json })) - ) -} diff --git a/src/services/state/viewerConfig.store.helper.ts b/src/services/state/viewerConfig.store.helper.ts deleted file mode 100644 index 719b4745979f04cc7df9c98172dbc17bdf09c965..0000000000000000000000000000000000000000 --- a/src/services/state/viewerConfig.store.helper.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createSelector } from "@ngrx/store" - -export const VIEWER_CONFIG_FEATURE_KEY = 'viewerConfigState' -export interface IViewerConfigState { - gpuLimit: number - animation: boolean - useMobileUI: boolean -} - -export const viewerConfigSelectorUseMobileUi = createSelector( - state => state[VIEWER_CONFIG_FEATURE_KEY], - viewerConfigState => viewerConfigState.useMobileUI -) diff --git a/src/services/state/viewerConfig.store.ts b/src/services/state/viewerConfig.store.ts deleted file mode 100644 index 27850b946bd2e0ca51f8616c91fd5a9b6ff52a23..0000000000000000000000000000000000000000 --- a/src/services/state/viewerConfig.store.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Action } from "@ngrx/store"; -import { LOCAL_STORAGE_CONST } from "src/util/constants"; - -import { IViewerConfigState as StateInterface } from './viewerConfig.store.helper' -import { actionSetMobileUi } from "./viewerState/actions"; -export { StateInterface } - -interface ViewerConfigurationAction extends Action { - config: Partial<StateInterface> - payload: any -} - -export const CONFIG_CONSTANTS = { - /** - * byets - */ - gpuLimitMin: 1e8, - gpuLimitMax: 1e9, - defaultGpuLimit: 1e9, - defaultAnimation: true, -} - -export const VIEWER_CONFIG_ACTION_TYPES = { - SET_ANIMATION: `SET_ANIMATION`, - UPDATE_CONFIG: `UPDATE_CONFIG`, - SET_MOBILE_UI: actionSetMobileUi.type, -} - -// get gpu limit -const lsGpuLimit = localStorage.getItem(LOCAL_STORAGE_CONST.GPU_LIMIT) -const lsAnimationFlag = localStorage.getItem(LOCAL_STORAGE_CONST.ANIMATION) -const gpuLimit = lsGpuLimit && !isNaN(Number(lsGpuLimit)) - ? Number(lsGpuLimit) - : CONFIG_CONSTANTS.defaultGpuLimit - -// get animation flag -const animation = lsAnimationFlag && lsAnimationFlag === 'true' - ? true - : lsAnimationFlag === 'false' - ? false - : CONFIG_CONSTANTS.defaultAnimation - -// get mobile ui setting -// UA sniff only if not useMobileUI not explicitly set -const getIsMobile = () => { - // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/maxTouchPoints - // CC0 or MIT - // msMaxTouchPoints is not needed, since IE is not supported - return 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0 -} -const useMobileUIStroageValue = window && window.localStorage && window.localStorage.getItem(LOCAL_STORAGE_CONST.MOBILE_UI) - -export const defaultState: StateInterface = { - animation, - gpuLimit, - useMobileUI: (useMobileUIStroageValue && useMobileUIStroageValue === 'true') || getIsMobile(), -} - -export const getStateStore = ({ state = defaultState } = {}) => (prevState: StateInterface = state, action: ViewerConfigurationAction) => { - switch (action.type) { - case VIEWER_CONFIG_ACTION_TYPES.SET_MOBILE_UI: { - const { payload } = action - const { useMobileUI } = payload - return { - ...prevState, - useMobileUI, - } - } - case VIEWER_CONFIG_ACTION_TYPES.UPDATE_CONFIG: - return { - ...prevState, - ...action.config, - } - default: return prevState - } -} - -// must export a named function for aot compilation -// see https://github.com/angular/angular/issues/15587 -// https://github.com/amcdnl/ngrx-actions/issues/23 -// or just google for: -// -// angular function expressions are not supported in decorators - -const defaultStateStore = getStateStore() - -export function stateStore(state, action) { - return defaultStateStore(state, action) -} diff --git a/src/services/state/viewerConfig/selectors.ts b/src/services/state/viewerConfig/selectors.ts deleted file mode 100644 index 4b69c0e58e84b67d30b509f09c8f6df9ff67b787..0000000000000000000000000000000000000000 --- a/src/services/state/viewerConfig/selectors.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createSelector } from "@ngrx/store"; - -export const selectViewerConfigAnimationFlag = createSelector( - state => state['viewerConfigState'], - viewerConfigState => viewerConfigState['animation'] -) diff --git a/src/services/state/viewerState.store.helper.spec.ts b/src/services/state/viewerState.store.helper.spec.ts deleted file mode 100644 index fa0845554d221ea0051658ba8d096145913be883..0000000000000000000000000000000000000000 --- a/src/services/state/viewerState.store.helper.spec.ts +++ /dev/null @@ -1,346 +0,0 @@ -import { TestBed } from "@angular/core/testing" -import { Action } from "@ngrx/store" -import { provideMockActions } from "@ngrx/effects/testing" -import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { Observable, of } from "rxjs" -import { isNewerThan, ViewerStateHelperEffect } from "./viewerState.store.helper" -import { viewerStateGetSelectedAtlas, viewerStateSelectedTemplateSelector } from "./viewerState/selectors" -import { viewerStateHelperSelectParcellationWithId, viewerStateRemoveAdditionalLayer } from "./viewerState/actions" -import { generalActionError } from "../stateStore.helper" -import { hot } from "jasmine-marbles" - -describe('> viewerState.store.helper.ts', () => { - const tmplId = 'test-tmpl-id' - const tmplId0 = 'test-tmpl-id-0' - describe('> ViewerStateHelperEffect', () => { - let effect: ViewerStateHelperEffect - let mockStore: MockStore - let actions$: Observable<Action> - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - ViewerStateHelperEffect, - provideMockStore(), - provideMockActions(() => actions$) - ] - }) - - mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, { - ['@id']: tmplId - }) - - actions$ = of( - viewerStateRemoveAdditionalLayer({ - payload: { - ['@id']: 'bla' - } - }) - ) - }) - - describe('> if selected atlas has no matching tmpl space', () => { - beforeEach(() => { - mockStore.overrideSelector(viewerStateGetSelectedAtlas, { - templateSpaces: [{ - ['@id']: tmplId0 - }] - }) - }) - it('> should emit gernal error', () => { - effect = TestBed.inject(ViewerStateHelperEffect) - effect.onRemoveAdditionalLayer$.subscribe(val => { - expect(val.type === generalActionError.type) - }) - }) - }) - - describe('> if selected atlas has matching tmpl', () => { - - const parcId0 = 'test-parc-id-0' - const parcId1 = 'test-parc-id-1' - const tmpSp = { - ['@id']: tmplId, - availableIn: [{ - ['@id']: parcId0 - }], - } - beforeEach(() => { - mockStore.overrideSelector(viewerStateGetSelectedAtlas, { - templateSpaces: [ - tmpSp - ], - parcellations: [], - }) - }) - - describe('> if parc is empty array', () => { - it('> should emit with falsy as payload', () => { - effect = TestBed.inject(ViewerStateHelperEffect) - expect( - effect.onRemoveAdditionalLayer$ - ).toBeObservable( - hot('(a|)', { - a: viewerStateHelperSelectParcellationWithId({ - payload: undefined - }) - }) - ) - }) - }) - describe('> if no parc has eligible @id', () => { - beforeEach(() => { - mockStore.overrideSelector(viewerStateGetSelectedAtlas, { - templateSpaces: [ - tmpSp - ], - parcellations: [{ - ['@id']: parcId1 - }] - }) - }) - it('> should emit with falsy as payload', () => { - effect = TestBed.inject(ViewerStateHelperEffect) - expect( - effect.onRemoveAdditionalLayer$ - ).toBeObservable( - hot('(a|)', { - a: viewerStateHelperSelectParcellationWithId({ - payload: undefined - }) - }) - ) - }) - }) - - describe('> if some parc has eligible @id', () => { - describe('> if no @version is available', () => { - const parc1 = { - ['@id']: parcId0, - name: 'p0-0', - baseLayer: true - } - const parc2 = { - ['@id']: parcId0, - name: 'p0-1', - baseLayer: true - } - beforeEach(() => { - - mockStore.overrideSelector(viewerStateGetSelectedAtlas, { - templateSpaces: [ - tmpSp - ], - parcellations: [ - parc1, - parc2 - ] - }) - }) - it('> selects the first parc', () => { - - effect = TestBed.inject(ViewerStateHelperEffect) - expect( - effect.onRemoveAdditionalLayer$ - ).toBeObservable( - hot('(a|)', { - a: viewerStateHelperSelectParcellationWithId({ - payload: parc1 - }) - }) - ) - }) - }) - - describe('> if @version is available', () => { - - describe('> if there exist an entry without @next attribute', () => { - - const parc1 = { - ['@id']: parcId0, - name: 'p0-0', - baseLayer: true, - ['@version']: { - ['@next']: 'random-value' - } - } - const parc2 = { - ['@id']: parcId0, - name: 'p0-1', - baseLayer: true, - ['@version']: { - ['@next']: null - } - } - beforeEach(() => { - - mockStore.overrideSelector(viewerStateGetSelectedAtlas, { - templateSpaces: [ - tmpSp - ], - parcellations: [ - parc1, - parc2 - ] - }) - }) - it('> selects the first one without @next attribute', () => { - - effect = TestBed.inject(ViewerStateHelperEffect) - expect( - effect.onRemoveAdditionalLayer$ - ).toBeObservable( - hot('(a|)', { - a: viewerStateHelperSelectParcellationWithId({ - payload: parc2 - }) - }) - ) - }) - }) - describe('> if there exist no entry without @next attribute', () => { - - const parc1 = { - ['@id']: parcId0, - name: 'p0-0', - baseLayer: true, - ['@version']: { - ['@next']: 'random-value' - } - } - const parc2 = { - ['@id']: parcId0, - name: 'p0-1', - baseLayer: true, - ['@version']: { - ['@next']: 'another-random-value' - } - } - beforeEach(() => { - - mockStore.overrideSelector(viewerStateGetSelectedAtlas, { - templateSpaces: [ - tmpSp - ], - parcellations: [ - parc1, - parc2 - ] - }) - }) - it('> selects the first one without @next attribute', () => { - - effect = TestBed.inject(ViewerStateHelperEffect) - expect( - effect.onRemoveAdditionalLayer$ - ).toBeObservable( - hot('(a|)', { - a: viewerStateHelperSelectParcellationWithId({ - payload: parc1 - }) - }) - ) - }) - }) - }) - }) - }) - }) - - describe('> isNewerThan', () => { - describe('> ill formed versions', () => { - it('> in circular references, throws', () => { - - const parc0Circular = { - - "@version": { - "@next": "aaa-bbb", - "@this": "ccc-ddd", - "name": "", - "@previous": null, - } - } - const parc1Circular = { - - "@version": { - "@next": "ccc-ddd", - "@this": "aaa-bbb", - "name": "", - "@previous": null, - } - } - const p2 = { - ["@id"]: "foo-bar" - } - const p3 = { - ["@id"]: "baz" - } - expect(() => { - isNewerThan([parc0Circular, parc1Circular], p2, p3) - }).toThrow() - }) - - it('> if not found, will throw', () => { - - const parc0Circular = { - - "@version": { - "@next": "aaa-bbb", - "@this": "ccc-ddd", - "name": "", - "@previous": null, - } - } - const parc1Circular = { - - "@version": { - "@next": null, - "@this": "aaa-bbb", - "name": "", - "@previous": null, - } - } - const p2 = { - ["@id"]: "foo-bar" - } - const p3 = { - ["@id"]: "baz" - } - expect(() => { - isNewerThan([parc0Circular, parc1Circular], p2, p3) - }).toThrow() - }) - }) - - it('> works on well formed versions', () => { - - const parc0 = { - "@version": { - "@next": null, - "@this": "aaa-bbb", - "name": "", - "@previous": "ccc-ddd", - } - } - const parc1 = { - "@version": { - "@next": "aaa-bbb", - "@this": "ccc-ddd", - "name": "", - "@previous": null, - } - } - - const p0 = { - ['@id']: 'aaa-bbb' - } - const p1 = { - ['@id']: 'ccc-ddd' - } - expect( - isNewerThan([parc0, parc1], p0, p1) - ).toBeTrue() - }) - - }) -}) \ No newline at end of file diff --git a/src/services/state/viewerState.store.helper.ts b/src/services/state/viewerState.store.helper.ts deleted file mode 100644 index bf5f69fb89b4e9dbed57fd4066927a0254758944..0000000000000000000000000000000000000000 --- a/src/services/state/viewerState.store.helper.ts +++ /dev/null @@ -1,205 +0,0 @@ -// TODO merge with viewerstate.store.ts when refactor is done -import { createReducer, on, ActionReducer, Store, select } from "@ngrx/store"; -import { generalActionError, generalApplyState } from "../stateStore.helper"; -import { Effect, Actions, ofType } from "@ngrx/effects"; -import { Observable } from "rxjs"; -import { withLatestFrom, map } from "rxjs/operators"; -import { Injectable } from "@angular/core"; - -import { - viewerStateNewViewer, - viewerStateHelperSelectParcellationWithId, - viewerStateNavigateToRegion, - viewerStateRemoveAdditionalLayer, - viewerStateSelectAtlas, - viewerStateSelectParcellation, - viewerStateSelectTemplateWithId, - viewerStateSetConnectivityRegion, - viewerStateNehubaLayerchanged, - viewerStateSetFetchedAtlases, - viewerStateSetSelectedRegions, - viewerStateSetSelectedRegionsWithIds, - viewerStateToggleLayer, - viewerStateToggleRegionSelect, - viewerStateSelectRegionWithIdDeprecated, - viewerStateSetViewerMode, - viewerStateDblClickOnViewer, - viewerStateAddUserLandmarks, - viewreStateRemoveUserLandmarks, - viewerStateMouseOverCustomLandmark, - viewerStateMouseOverCustomLandmarkInPerspectiveView, - viewerStateSelectTemplateWithName, -} from './viewerState/actions' - -export { - viewerStateNewViewer, - viewerStateHelperSelectParcellationWithId, - viewerStateNavigateToRegion, - viewerStateRemoveAdditionalLayer, - viewerStateSelectAtlas, - viewerStateSelectParcellation, - viewerStateSelectTemplateWithId, - viewerStateSetConnectivityRegion, - viewerStateNehubaLayerchanged, - viewerStateSetFetchedAtlases, - viewerStateSetSelectedRegions, - viewerStateSetSelectedRegionsWithIds, - viewerStateToggleLayer, - viewerStateToggleRegionSelect, - viewerStateSelectRegionWithIdDeprecated, - viewerStateSetViewerMode, - viewerStateDblClickOnViewer, - viewerStateAddUserLandmarks, - viewreStateRemoveUserLandmarks, - viewerStateMouseOverCustomLandmark, - viewerStateMouseOverCustomLandmarkInPerspectiveView, - viewerStateSelectTemplateWithName, -} - -import { - viewerStateSelectedRegionsSelector, - viewerStateSelectedTemplateSelector, - viewerStateSelectedParcellationSelector, - viewerStateGetSelectedAtlas, - viewerStateCustomLandmarkSelector, - viewerStateFetchedTemplatesSelector, - viewerStateNavigationStateSelector, -} from './viewerState/selectors' -import { IHasId } from "src/util/interfaces"; - -export { - viewerStateSelectedRegionsSelector, - viewerStateSelectedTemplateSelector, - viewerStateSelectedParcellationSelector, - viewerStateCustomLandmarkSelector, - viewerStateFetchedTemplatesSelector, - viewerStateNavigationStateSelector, -} - -interface IViewerStateHelperStore{ - fetchedAtlases: any[] - selectedAtlasId: string - overlayingAdditionalParcellations: any[] -} - -const initialState: IViewerStateHelperStore = { - fetchedAtlases: [], - selectedAtlasId: null, - overlayingAdditionalParcellations: [] -} - -function handleToggleLayerAction(reducer: ActionReducer<any>): ActionReducer<any>{ - return function(state, action) { - switch(action.type){ - case viewerStateToggleLayer.type: { - const { payload } = action as any - const { templateSelected } = (state && state['viewerState']) || {} - - const selectParcellation = templateSelected?.parcellations.find(p => p['@id'] === payload['@id']) - return reducer(state, viewerStateSelectParcellation({ selectParcellation })) - } - default: reducer(state, action) - } - return reducer(state, action) - } -} - -export const viewerStateMetaReducers = [ - handleToggleLayerAction -] - -@Injectable({ - providedIn: 'root' -}) - -export class ViewerStateHelperEffect{ - @Effect() - onRemoveAdditionalLayer$: Observable<any> = this.actions$.pipe( - ofType(viewerStateRemoveAdditionalLayer.type), - withLatestFrom( - this.store$.pipe( - select(viewerStateGetSelectedAtlas) - ), - this.store$.pipe( - select(viewerStateSelectedTemplateSelector) - ) - ), - map(([ { payload }, selectedAtlas, selectedTemplate ]) => { - const tmpl = selectedAtlas['templateSpaces'].find(t => t['@id'] === selectedTemplate['@id']) - if (!tmpl) { - return generalActionError({ - message: `templateSpace with id ${selectedTemplate['@id']} cannot be found in atlas with id ${selectedAtlas['@id']}` - }) - } - - const eligibleParcIdSet = new Set( - tmpl.availableIn.map(p => p['@id']) - ) - const baseLayers = selectedAtlas['parcellations'].filter(fullP => fullP['baseLayer'] && eligibleParcIdSet.has(fullP['@id'])) - const baseLayer = baseLayers.find(layer => !!layer['@version'] && !layer['@version']['@next']) || baseLayers[0] - return viewerStateHelperSelectParcellationWithId({ payload: baseLayer }) - }) - ) - - constructor( - private store$: Store<any>, - private actions$: Actions - ){ - - } -} - -export const viewerStateHelperReducer = createReducer( - initialState, - on(viewerStateSetFetchedAtlases, (state, { fetchedAtlases }) => ({ ...state, fetchedAtlases })), - on(viewerStateSelectAtlas, (state, { atlas }) => ({ ...state, selectedAtlasId: atlas['@id'] })), - on(generalApplyState, (_prevState, { state }) => ({ ...state[viewerStateHelperStoreName] })), -) - -export const viewerStateHelperStoreName = 'viewerStateHelper' - -export const defaultState = initialState - -interface IVersion{ - "@next": string - "@this": string - "name": string - "@previous": string -} - -interface IHasVersion{ - ['@version']: IVersion -} - -export function isNewerThan(arr: IHasVersion[], srcObj: IHasId, compObj: IHasId): boolean { - - function* GenNewerVersions(flag){ - let it = 0 - const newest = arr.find((v => v['@version'] && v['@version']['@this'] === srcObj['@id'])) - if (!newest) throw new Error(`GenNewerVersions error newest element isn't found`) - yield newest - let currPreviousId = newest['@version'][ flag ? '@next' : '@previous' ] - while (currPreviousId) { - it += 1 - if (it>100) throw new Error(`iteration excced 100, did you include a loop?`) - - const curr = arr.find(v => v['@version']['@this'] === currPreviousId) - if (!curr) throw new Error(`GenNewerVersions error, version id ${currPreviousId} not found`) - currPreviousId = curr['@version'][ flag ? '@next' : '@previous' ] - yield curr - } - } - for (const obj of GenNewerVersions(true)) { - if (obj['@version']['@this'] === compObj['@id']) { - return false - } - } - - for (const obj of GenNewerVersions(false)) { - if (obj['@version']['@this'] === compObj['@id']) { - return true - } - } - - throw new Error(`isNewerThan error, neither srcObj nor compObj exist in array`) -} diff --git a/src/services/state/viewerState.store.ts b/src/services/state/viewerState.store.ts deleted file mode 100644 index ed2422c70f8c2746d8e6d79fb903cb866f126452..0000000000000000000000000000000000000000 --- a/src/services/state/viewerState.store.ts +++ /dev/null @@ -1,470 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; -import { Action, select, Store } from '@ngrx/store' -import { Observable } from 'rxjs'; -import { distinctUntilChanged, filter, map, shareReplay, startWith, withLatestFrom, mapTo } from 'rxjs/operators'; -import { IUserLandmark } from 'src/atlasViewer/atlasViewer.apiService.service'; -import { INgLayerInterface } from 'src/atlasViewer/atlasViewer.component'; -import { getViewer } from 'src/util/fn'; -import { LoggingService } from 'src/logging'; -import { IavRootStoreInterface } from '../stateStore.service'; -import { GENERAL_ACTION_TYPES } from '../stateStore.service' -import { CLOSE_SIDE_PANEL } from './uiState.store'; -import { - viewerStateSetSelectedRegions, - viewerStateSetConnectivityRegion, - viewerStateSelectParcellation, - viewerStateSelectRegionWithIdDeprecated, - viewerStateCustomLandmarkSelector, - viewerStateDblClickOnViewer, - viewerStateAddUserLandmarks, - viewreStateRemoveUserLandmarks, - viewerStateMouseOverCustomLandmark, - viewerStateMouseOverCustomLandmarkInPerspectiveView, - viewerStateNewViewer -} from './viewerState.store.helper'; -import { cvtNehubaConfigToNavigationObj } from 'src/state'; -import { - viewerStateChangeNavigation, - viewerStateNehubaLayerchanged, - viewerStateSetViewerMode, - actionSelectLandmarks -} from './viewerState/actions'; -import { serialiseParcellationRegion } from "common/util" - -export interface StateInterface { - fetchedTemplates: any[] - - templateSelected: any | null - parcellationSelected: any | null - regionsSelected: any[] - - viewerMode: string - - landmarksSelected: any[] - userLandmarks: IUserLandmark[] - - navigation: any | null - dedicatedView: string[] - - loadedNgLayers: INgLayerInterface[] - connectivityRegion: string | null - overwrittenColorMap: string | null - - standaloneVolumes: any[] -} - -export interface ActionInterface extends Action { - fetchedTemplate?: any[] - - selectTemplate?: any - selectParcellation?: any - selectRegions?: any[] - selectRegionIds: string[] - deselectRegions?: any[] - dedicatedView?: string - - updatedParcellation?: any - - landmarks: IUserLandmark[] - deselectLandmarks: IUserLandmark[] - - navigation?: any - - payload: any - - connectivityRegion?: string -} - -export const defaultState: StateInterface = { - - landmarksSelected : [], - fetchedTemplates : [], - loadedNgLayers: [], - regionsSelected: [], - viewerMode: null, - userLandmarks: [], - dedicatedView: null, - navigation: null, - parcellationSelected: null, - templateSelected: null, - connectivityRegion: '', - overwrittenColorMap: null, - standaloneVolumes: [] -} - -export const getStateStore = ({ state = defaultState } = {}) => (prevState: Partial<StateInterface> = state, action: ActionInterface) => { - switch (action.type) { - /** - * TODO may be obsolete. test when nifti become available - */ - case LOAD_DEDICATED_LAYER: { - const dedicatedView = prevState.dedicatedView - ? prevState.dedicatedView.concat(action.dedicatedView) - : [action.dedicatedView] - return { - ...prevState, - dedicatedView, - } - } - case UNLOAD_DEDICATED_LAYER: - return { - ...prevState, - dedicatedView : prevState.dedicatedView - ? prevState.dedicatedView.filter(dv => dv !== action.dedicatedView) - : [], - } - case CLEAR_STANDALONE_VOLUMES: - return { - ...prevState, - standaloneVolumes: [] - } - case viewerStateNewViewer.type: { - - const { - selectParcellation: parcellation, - navigation, - selectTemplate, - } = action - const navigationFromTemplateSelected = cvtNehubaConfigToNavigationObj(selectTemplate?.nehubaConfig?.dataset?.initialNgState) - return { - ...prevState, - templateSelected : selectTemplate, - parcellationSelected : parcellation, - // taken care of by effect.ts - // regionsSelected : [], - - // taken care of by effect.ts - // landmarksSelected : [], - navigation : navigation || navigationFromTemplateSelected, - dedicatedView : null, - } - } - case FETCHED_TEMPLATE : { - return { - ...prevState, - fetchedTemplates: prevState.fetchedTemplates.concat(action.fetchedTemplate), - } - } - case viewerStateChangeNavigation.type: - case CHANGE_NAVIGATION : { - return { - ...prevState, - navigation : action.navigation, - } - } - case viewerStateSelectParcellation.type: - case SELECT_PARCELLATION : { - const { selectParcellation } = action - return { - ...prevState, - parcellationSelected: selectParcellation, - // taken care of by effect.ts - // regionsSelected: [] - } - } - case viewerStateSetSelectedRegions.type: - case SELECT_REGIONS: { - const { selectRegions } = action - return { - ...prevState, - regionsSelected: selectRegions, - } - } - case viewerStateSetViewerMode.type: { - return { - ...prevState, - viewerMode: action.payload - } - } - case DESELECT_LANDMARKS : { - return { - ...prevState, - landmarksSelected : prevState.landmarksSelected.filter(lm => action.deselectLandmarks.findIndex(dLm => dLm.name === lm.name) < 0), - } - } - case actionSelectLandmarks.type: { - return { - ...prevState, - landmarksSelected : action.landmarks, - } - } - case USER_LANDMARKS : { - return { - ...prevState, - userLandmarks: action.landmarks, - } - } - /** - * TODO - * duplicated with ngViewerState.layers ? - */ - case viewerStateNehubaLayerchanged.type: { - const viewer = getViewer() - if (!viewer) { - return { - ...prevState, - loadedNgLayers: [], - } - } else { - return { - ...prevState, - loadedNgLayers: (viewer.layerManager.managedLayers as any[]).map(obj => ({ - name : obj.name, - type : obj.initialSpecification.type, - source : obj.sourceUrl, - visible : obj.visible, - }) as INgLayerInterface), - } - } - } - case GENERAL_ACTION_TYPES.APPLY_STATE: { - const { viewerState } = (action as any).state - return viewerState - } - case viewerStateSetConnectivityRegion.type: - case SET_CONNECTIVITY_REGION: - return { - ...prevState, - connectivityRegion: action.connectivityRegion, - } - case CLEAR_CONNECTIVITY_REGION: - return { - ...prevState, - connectivityRegion: '', - } - case SET_OVERWRITTEN_COLOR_MAP: - return { - ...prevState, - overwrittenColorMap: action.payload || '', - } - default : - return prevState - } -} - -// must export a named function for aot compilation -// see https://github.com/angular/angular/issues/15587 -// https://github.com/amcdnl/ngrx-actions/issues/23 -// or just google for: -// -// angular function expressions are not supported in decorators - -const defaultStateStore = getStateStore() - -export function stateStore(state, action) { - return defaultStateStore(state, action) -} - -export const LOAD_DEDICATED_LAYER = 'LOAD_DEDICATED_LAYER' -export const UNLOAD_DEDICATED_LAYER = 'UNLOAD_DEDICATED_LAYER' - -export const FETCHED_TEMPLATE = 'FETCHED_TEMPLATE' -export const CHANGE_NAVIGATION = viewerStateChangeNavigation.type - -export const SELECT_PARCELLATION = viewerStateSelectParcellation.type - -export const DESELECT_REGIONS = `DESELECT_REGIONS` -export const SELECT_REGIONS_WITH_ID = viewerStateSelectRegionWithIdDeprecated.type -// export const SET_VIEWER_MODE = viewerStateSetViewerMode.type -export const SELECT_LANDMARKS = `SELECT_LANDMARKS` -export const SELECT_REGIONS = viewerStateSetSelectedRegions.type -export const DESELECT_LANDMARKS = `DESELECT_LANDMARKS` -export const USER_LANDMARKS = `USER_LANDMARKS` - -export const SET_CONNECTIVITY_REGION = `SET_CONNECTIVITY_REGION` -export const CLEAR_CONNECTIVITY_REGION = `CLEAR_CONNECTIVITY_REGION` -export const SET_OVERWRITTEN_COLOR_MAP = `SET_OVERWRITTEN_COLOR_MAP` -export const CLEAR_STANDALONE_VOLUMES = `CLEAR_STANDALONE_VOLUMES` - -@Injectable({ - providedIn: 'root', -}) - -export class ViewerStateUseEffect { - constructor( - private actions$: Actions, - private store$: Store<IavRootStoreInterface>, - private log: LoggingService, - ) { - - const viewerState$ = this.store$.pipe( - select('viewerState'), - shareReplay(1) - ) - this.currentLandmarks$ = this.store$.pipe( - select(viewerStateCustomLandmarkSelector), - shareReplay(1), - ) - - this.removeUserLandmarks = this.actions$.pipe( - ofType(ACTION_TYPES.REMOVE_USER_LANDMARKS), - withLatestFrom(this.currentLandmarks$), - map(([action, currentLandmarks]) => { - const { landmarkIds } = (action as ActionInterface).payload - for ( const rmId of landmarkIds ) { - const idx = currentLandmarks.findIndex(({ id }) => id === rmId) - if (idx < 0) { this.log.warn(`remove userlandmark with id ${rmId} does not exist`) } - } - const removeSet = new Set(landmarkIds) - return { - type: USER_LANDMARKS, - landmarks: currentLandmarks.filter(({ id }) => !removeSet.has(id)), - } - }), - ) - - this.addUserLandmarks$ = this.actions$.pipe( - ofType(viewerStateAddUserLandmarks.type), - withLatestFrom(this.currentLandmarks$), - map(([action, currentLandmarks]) => { - const { landmarks } = action as ActionInterface - const landmarkMap = new Map() - for (const landmark of currentLandmarks) { - const { id } = landmark - landmarkMap.set(id, landmark) - } - for (const landmark of landmarks) { - const { id } = landmark - if (landmarkMap.has(id)) { - this.log.warn(`Attempting to add a landmark that already exists, id: ${id}`) - } else { - landmarkMap.set(id, landmark) - } - } - const userLandmarks = Array.from(landmarkMap).map(([_id, landmark]) => landmark) - return { - type: USER_LANDMARKS, - landmarks: userLandmarks, - } - }), - ) - - this.mouseoverUserLandmarks = this.actions$.pipe( - ofType(viewerStateMouseOverCustomLandmarkInPerspectiveView.type), - withLatestFrom(this.currentLandmarks$), - map(([ action, currentLandmarks ]) => { - const { payload } = action as any - const { label } = payload - if (!label) { - return viewerStateMouseOverCustomLandmark({ - payload: { - userLandmark: null - } - }) - } - - const idx = Number(label.replace('label=', '')) - if (isNaN(idx)) { - this.log.warn(`Landmark index could not be parsed as a number: ${idx}`) - return viewerStateMouseOverCustomLandmark({ - payload: { userLandmark: null } - }) - } - return viewerStateMouseOverCustomLandmark({ - payload: { - userLandmark: currentLandmarks[idx] - } - }) - }), - - ) - - const doubleClickOnViewer$ = this.actions$.pipe( - ofType(ACTION_TYPES.DOUBLE_CLICK_ON_VIEWER), - map(action => { - const { payload } = action as any - const { segments, landmark, userLandmark } = payload - return { segments, landmark, userLandmark } - }), - shareReplay(1), - ) - - this.doubleClickOnViewerToggleRegions$ = doubleClickOnViewer$.pipe( - filter(({ segments }) => segments && segments.length > 0), - withLatestFrom(viewerState$.pipe( - select('regionsSelected'), - distinctUntilChanged(), - startWith([]), - )), - map(([{ segments }, regionsSelected]) => { - const selectedSet = new Set<string>(regionsSelected.map(serialiseParcellationRegion)) - const toggleArr = segments.map(({ segment, layer }) => serialiseParcellationRegion({ ngId: layer.name, ...segment })) - - const deleteFlag = toggleArr.some(id => selectedSet.has(id)) - - for (const id of toggleArr) { - if (deleteFlag) { selectedSet.delete(id) } else { selectedSet.add(id) } - } - - return viewerStateSelectRegionWithIdDeprecated({ - selectRegionIds: [...selectedSet], - }) - }), - ) - - this.doubleClickOnViewerToggleLandmark$ = doubleClickOnViewer$.pipe( - filter(({ landmark }) => !!landmark), - withLatestFrom(viewerState$.pipe( - select('landmarksSelected'), - startWith([]), - )), - map(([{ landmark }, selectedSpatialDatas]) => { - - const selectedIdx = selectedSpatialDatas.findIndex(data => data.name === landmark.name) - - const newSelectedSpatialDatas = selectedIdx >= 0 - ? selectedSpatialDatas.filter((_, idx) => idx !== selectedIdx) - : selectedSpatialDatas.concat(landmark) - - return actionSelectLandmarks({ - landmarks: newSelectedSpatialDatas, - }) - }), - ) - - this.doubleClickOnViewerToogleUserLandmark$ = doubleClickOnViewer$.pipe( - filter(({ userLandmark }) => userLandmark), - ) - - this.onStandAloneVolumesExistCloseMatDrawer$ = viewerState$.pipe( - select('standaloneVolumes'), - filter(v => v && Array.isArray(v) && v.length > 0), - mapTo({ - type: CLOSE_SIDE_PANEL - }) - ) - } - - private currentLandmarks$: Observable<any[]> - - @Effect() - public onStandAloneVolumesExistCloseMatDrawer$: Observable<any> - - @Effect() - public mouseoverUserLandmarks: Observable<any> - - @Effect() - public removeUserLandmarks: Observable<any> - - @Effect() - public addUserLandmarks$: Observable<any> - - @Effect() - public doubleClickOnViewerToggleRegions$: Observable<any> - - @Effect() - public doubleClickOnViewerToggleLandmark$: Observable<any> - - // @Effect() - public doubleClickOnViewerToogleUserLandmark$: Observable<any> -} - -const ACTION_TYPES = { - REMOVE_USER_LANDMARKS: viewreStateRemoveUserLandmarks.type, - - SINGLE_CLICK_ON_VIEWER: 'SINGLE_CLICK_ON_VIEWER', - DOUBLE_CLICK_ON_VIEWER: viewerStateDblClickOnViewer.type -} - -export const VIEWERSTATE_ACTION_TYPES = ACTION_TYPES diff --git a/src/services/state/viewerState/actions.ts b/src/services/state/viewerState/actions.ts deleted file mode 100644 index d20ff0cd29af5855535d0ca27fc519e9204aa360..0000000000000000000000000000000000000000 --- a/src/services/state/viewerState/actions.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { createAction, props } from "@ngrx/store" -import { IRegion } from './constants' - -export const viewerStateNewViewer = createAction( - `[viewerState] newViewer`, - props<{ - selectTemplate: any - selectParcellation: any - navigation?: any - }>() -) - -export const viewerStateSetSelectedRegionsWithIds = createAction( - `[viewerState] setSelectedRegionsWithIds`, - props<{ selectRegionIds: string[] }>() -) - -export const viewerStateSetSelectedRegions = createAction( - '[viewerState] setSelectedRegions', - props<{ selectRegions: IRegion[] }>() -) - -export const viewerStateSetConnectivityRegion = createAction( - `[viewerState] setConnectivityRegion`, - props<{ connectivityRegion: any }>() -) - -export const viewerStateNehubaLayerchanged = createAction( - `[viewerState] nehubaLayerChanged`, -) - -export const viewerStateNavigateToRegion = createAction( - `[viewerState] navigateToRegion`, - props<{ payload: { region: any } }>() -) - -export const viewerStateToggleRegionSelect = createAction( - `[viewerState] toggleRegionSelect`, - props<{ payload: { region: any } }>() -) - -export const viewerStateSetFetchedAtlases = createAction( - '[viewerState] setFetchedatlases', - props<{ fetchedAtlases: any[] }>() -) - -export const viewerStateSelectAtlas = createAction( - `[viewerState] selectAtlas`, - props<{ - atlas: { - ['@id']: string - template?: { - ['@id']: string - } - } - }>() -) - -export const viewerStateHelperSelectParcellationWithId = createAction( - `[viewerStateHelper] selectParcellationWithId`, - props<{ payload: { ['@id']: string } }>() -) - -export const viewerStateSelectParcellation = createAction( - `[viewerState] selectParcellation`, - props<{ selectParcellation: any }>() -) - -export const viewerStateSelectTemplateWithName = createAction( - `[viewerState] selectTemplateWithName`, - props<{ payload: { name: string } }>() -) - -export const viewerStateSelectTemplateWithId = createAction( - `[viewerState] selectTemplateWithId`, - props<{ payload: { ['@id']: string }, config?: { selectParcellation: { ['@id']: string } } }>() -) - -export const viewerStateToggleLayer = createAction( - `[viewerState] toggleLayer`, - props<{ payload: { ['@id']: string } }>() -) - -export const viewerStateRemoveAdditionalLayer = createAction( - `[viewerState] removeAdditionalLayer`, - props<{ payload?: { ['@id']: string } }>() -) - -export const viewerStateSelectRegionWithIdDeprecated = createAction( - `[viewerState] [deprecated] selectRegionsWithId`, - props<{ selectRegionIds: string[] }>() -) - -export const viewerStateSetViewerMode = createAction( - `[viewerState] setViewerMode`, - props<{payload: string}>() -) - -export const viewerStateDblClickOnViewer = createAction( - `[viewerState] dblClickOnViewer`, - props<{ payload: { annotation: any, segments: any, landmark: any, userLandmark: any } }>() -) - -export const viewerStateAddUserLandmarks = createAction( - `[viewerState] addUserlandmark,`, - props<{ landmarks: any[] }>() -) - -export const viewreStateRemoveUserLandmarks = createAction( - `[viewerState] removeUserLandmarks`, - props<{ payload: { landmarkIds: string[] } }>() -) - -export const viewerStateMouseOverCustomLandmark = createAction( - '[viewerState] mouseOverCustomLandmark', - props<{ payload: { userLandmark: any } }>() -) - -export const viewerStateMouseOverCustomLandmarkInPerspectiveView = createAction( - `[viewerState] mouseOverCustomLandmarkInPerspectiveView`, - props<{ payload: { label: string } }>() -) - -export const viewerStateChangeNavigation = createAction( - `[viewerState] changeNavigation`, - props<{ navigation: any }>() -) - -export const actionSetMobileUi = createAction( - `[viewerState] setMobileUi`, - props<{ payload: { useMobileUI: boolean } }>() -) - -export const actionAddToRegionsSelectionWithIds = createAction( - `[viewerState] addToRegionSelectionWithIds`, - props<{ - selectRegionIds: string[] - }>() -) - -export const actionSelectLandmarks = createAction( - `[viewerState] selectLandmarks`, - props<{ - landmarks: any[] - }>() -) diff --git a/src/services/state/viewerState/constants.ts b/src/services/state/viewerState/constants.ts deleted file mode 100644 index dbfa8c7800f0cc602fe1b1f942b7088552fe20ce..0000000000000000000000000000000000000000 --- a/src/services/state/viewerState/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface IRegion{ - name: string - [key: string]: string -} diff --git a/src/services/state/viewerState/selectors.spec.ts b/src/services/state/viewerState/selectors.spec.ts deleted file mode 100644 index 6f13c133919aa50774b6e72eb15f2f872e1dfcc8..0000000000000000000000000000000000000000 --- a/src/services/state/viewerState/selectors.spec.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { - viewerStateGetOverlayingAdditionalParcellations, - viewerStateAtlasParcellationSelector, - viewerStateAtlasLatestParcellationSelector, - viewerStateParcVersionSelector, -} from './selectors' - - -const atlas1 = { - '@id': 'atlas-1', - name: 'atlas-1-name', - templateSpaces: [ - { - '@id': 'atlas-1-tmpl-1', - name: 'atlas-1-tmpl-1', - availableIn: [ - { - '@id': 'atlas-1-parc-1', - name: 'atlas-1-parc-1' - }, - { - '@id': 'atlas-1-parc-2', - name: 'atlas-1-parc-2' - } - ] - } - ], - parcellations: [ - { - '@id': 'atlas-1-parc-1', - name: 'atlas-1-parc-1', - baseLayer: true - }, - { - '@id': 'atlas-1-parc-2', - name: 'atlas-1-parc-2' - } - ] -} - -const tmpl1 = { - '@id': 'atlas-1-tmpl-1', - name: 'atlas-1-tmpl-1', - parcellations: [ - { - '@id': 'atlas-1-parc-1', - name: 'atlas-1-parc-1' - }, - { - '@id': 'atlas-1-parc-2', - name: 'atlas-1-parc-2' - } - ] -} - -const atlas2 = { - '@id': 'atlas-2', - name: 'atlas-2-name', - templateSpaces: [ - { - '@id': 'atlas-2-tmpl-1', - name: 'atlas-2-tmpl-1', - availableIn: [ - { - '@id': 'atlas-2-parc-1', - name: 'atlas-2-parc-1' - }, - { - '@id': 'atlas-2-parc-2', - name: 'atlas-2-parc-2' - } - ] - }, - { - '@id': 'atlas-2-tmpl-2', - name: 'atlas-2-tmpl-2', - availableIn: [ - { - '@id': 'atlas-2-parc-1', - name: 'atlas-2-parc-1' - }, - { - '@id': 'atlas-2-parc-2', - name: 'atlas-2-parc-2' - } - ] - } - ], - parcellations: [ - { - '@id': 'atlas-2-parc-1', - name: 'atlas-2-parc-1', - "@version": { - "@next": "atlas-2-parc-2", - "@this": "atlas-2-parc-1", - "name": "atlas-2-parc-1", - "@previous": null - } - }, - { - '@id': 'atlas-2-parc-2', - name: 'atlas-2-parc-2', - "@version": { - "@next": null, - "@this": "atlas-2-parc-2", - "name": "atlas-2-parc-2", - "@previous": "atlas-2-parc-1" - } - } - ] -} - -const tmpl2 = { - '@id': 'atlas-2-tmpl-1', - name: 'atlas-2-tmpl-1', - parcellations: [ - { - '@id': 'atlas-2-parc-1', - name: 'atlas-2-parc-1' - }, - { - '@id': 'atlas-2-parc-2', - name: 'atlas-2-parc-2' - } - ] -} - - -const tmpl2_2 = { - '@id': 'atlas-2-tmpl-2', - name: 'atlas-2-tmpl-2', - parcellations: [ - { - '@id': 'atlas-2-parc-1', - name: 'atlas-2-parc-1' - }, - { - '@id': 'atlas-2-parc-2', - name: 'atlas-2-parc-2' - } - ] -} - -const fetchedAtlases = [ - atlas1, - atlas2 -] - -const fetchedTemplates = [ - tmpl1, - tmpl2, - tmpl2_2 -] - -describe('viewerState/selector.ts', () => { - describe('> viewerStateGetOverlayingAdditionalParcellations', () => { - describe('> if atlas has no basic layer', () => { - it('> should return empty array', () => { - - const parcs = viewerStateGetOverlayingAdditionalParcellations.projector({ - fetchedAtlases, - selectedAtlasId: atlas2['@id'] - }, { - parcellationSelected: tmpl2.parcellations[0] - }) - - expect(parcs).toEqual([]) - }) - }) - - describe('> if atlas has basic layer', () => { - describe('> if non basiclayer is selected', () => { - it('> should return non empty array', () => { - const parc = atlas1.parcellations.find(p => !p['baseLayer']) - const parcs = viewerStateGetOverlayingAdditionalParcellations.projector({ - fetchedAtlases, - selectedAtlasId: atlas1['@id'] - }, { - parcellationSelected: parc - }) - expect(parcs.length).toEqual(1) - expect(parcs[0]['@id']).toEqual(parc['@id']) - }) - }) - - describe('> if basic layer is selected', () => { - it('> should return empty array', () => { - const parc = atlas1.parcellations.find(p => !!p['baseLayer']) - const parcs = viewerStateGetOverlayingAdditionalParcellations.projector({ - fetchedAtlases, - selectedAtlasId: atlas1['@id'] - }, { - parcellationSelected: parc - }) - expect(parcs.length).toEqual(0) - }) - }) - }) - }) - - describe('> viewerStateAtlasParcellationSelector', () => { - const check = (atlasJson, templates) => { - - const parcs = viewerStateAtlasParcellationSelector.projector({ - fetchedAtlases, - selectedAtlasId: atlasJson['@id'] - }, { - fetchedTemplates - }) - const templateParcs = [] - for (const tmpl of templates) { - templateParcs.push(...tmpl.parcellations) - } - for (const parc of parcs) { - const firstHalf = templateParcs.find(p => p['@id'] === parc['@id']) - const secondHalf = atlasJson.parcellations.find(p => p['@id'] === parc['@id']) - expect(firstHalf).toBeTruthy() - expect(secondHalf).toBeTruthy() - //TODO compare strict equality of firsthalf+secondhalf with parc - } - } - - it('> should work', () => { - check(atlas1, [tmpl1, tmpl2, tmpl2_2]) - check(atlas2, [tmpl1, tmpl2, tmpl2_2]) - }) - }) - - describe('> viewerStateAtlasLatestParcellationSelector', () => { - it('> should only show 1 parc', () => { - const parcs = viewerStateAtlasLatestParcellationSelector.projector(atlas2.parcellations) - expect(parcs.length).toEqual(1) - }) - }) - - describe('> viewerStateParcVersionSelector', () => { - it('> should work', () => { - const parcs = viewerStateParcVersionSelector.projector(atlas2.parcellations, { - parcellationSelected: atlas2.parcellations[0] - }) - expect(parcs.length).toEqual(2) - - expect(parcs[0]['@version']['@next']).toBeFalsy() - expect(parcs[parcs.length-1]['@version']['@previous']).toBeFalsy() - }) - }) -}) \ No newline at end of file diff --git a/src/services/state/viewerState/selectors.ts b/src/services/state/viewerState/selectors.ts deleted file mode 100644 index 61cd2b7dd54d4f3275ea01c5aa63fbebe60e54f7..0000000000000000000000000000000000000000 --- a/src/services/state/viewerState/selectors.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { createSelector } from "@ngrx/store" -import { viewerStateHelperStoreName } from "../viewerState.store.helper" - -export const viewerStateSelectedRegionsSelector = createSelector( - state => state['viewerState'], - viewerState => viewerState['regionsSelected'] -) - -export const viewerStateCustomLandmarkSelector = createSelector( - state => state['viewerState'], - viewerState => viewerState['userLandmarks'] -) - -const flattenFetchedTemplatesIntoParcellationsReducer = (acc, curr) => { - const parcelations = (curr['parcellations'] || []).map(p => { - return { - ...p, - useTheme: curr['useTheme'] - } - }) - - return acc.concat( parcelations ) -} - -export const viewerStateFetchedTemplatesSelector = createSelector( - state => state['viewerState'], - viewerState => viewerState['fetchedTemplates'] -) - -export const viewerStateSelectedTemplateSelector = createSelector( - state => state['viewerState'], - viewerState => viewerState?.['templateSelected'] -) - -export const viewerStateSelectorStandaloneVolumes = createSelector( - state => state['viewerState'], - viewerState => viewerState['standaloneVolumes'] -) - -/** - * viewerStateSelectedTemplateSelector may have it navigation mutated to allow for initiliasation of viewer at the correct navigation - * in some circumstances, it may be required to get the original navigation object - */ -export const viewerStateSelectedTemplatePureSelector = createSelector( - viewerStateFetchedTemplatesSelector, - viewerStateSelectedTemplateSelector, - (fetchedTemplates, selectedTemplate) => { - if (!selectedTemplate) return null - return fetchedTemplates.find(t => t['@id'] === selectedTemplate['@id']) - } -) - -export const viewerStateSelectedParcellationSelector = createSelector( - state => state['viewerState'], - viewerState => viewerState['parcellationSelected'] -) - -export const viewerStateNavigationStateSelector = createSelector( - state => state['viewerState'], - viewerState => viewerState['navigation'] -) - -export const viewerStateAllRegionsFlattenedRegionSelector = createSelector( - viewerStateSelectedParcellationSelector, - parc => { - const returnArr = [] - const processRegion = region => { - const { children, ...rest } = region - returnArr.push({ ...rest }) - region.children && Array.isArray(region.children) && region.children.forEach(processRegion) - } - if (parc && parc.regions && Array.isArray(parc.regions)) { - parc.regions.forEach(processRegion) - } - return returnArr - } -) - -export const viewerStateOverwrittenColorMapSelector = createSelector( - state => state['viewerState'], - viewerState => viewerState['overwrittenColorMap'] -) - -export const viewerStateStandAloneVolumes = createSelector( - state => state['viewerState'], - viewerState => viewerState['standaloneVolumes'] -) - -export const viewerStateSelectorNavigation = createSelector( - state => state['viewerState'], - viewerState => viewerState['navigation'] -) - -export const viewerStateViewerModeSelector = createSelector( - state => state['viewerState'], - viewerState => viewerState['viewerMode'] -) - -export const viewerStateGetOverlayingAdditionalParcellations = createSelector( - state => state[viewerStateHelperStoreName], - state => state['viewerState'], - (viewerHelperState, viewerState ) => { - const { selectedAtlasId, fetchedAtlases } = viewerHelperState - const { parcellationSelected } = viewerState - const selectedAtlas = selectedAtlasId && fetchedAtlases.find(a => a['@id'] === selectedAtlasId) - const hasBaseLayer = selectedAtlas?.parcellations.find(p => p.baseLayer) - if (!hasBaseLayer) return [] - const atlasLayer = selectedAtlas?.parcellations.find(p => p['@id'] === (parcellationSelected && parcellationSelected['@id'])) - const isBaseLayer = atlasLayer && atlasLayer.baseLayer - return (!!atlasLayer && !isBaseLayer) ? [{ - ...(parcellationSelected || {} ), - ...atlasLayer - }] : [] - } -) - -export const viewerStateFetchedAtlasesSelector = createSelector( - state => state[viewerStateHelperStoreName], - helperState => helperState['fetchedAtlases'] -) - -export const viewerStateGetSelectedAtlas = createSelector( - state => state[viewerStateHelperStoreName], - helperState => { - if (!helperState) return null - const { selectedAtlasId, fetchedAtlases } = helperState - if (!selectedAtlasId) return null - return selectedAtlasId && fetchedAtlases.find(a => a['@id'] === selectedAtlasId) - } -) - -export const viewerStateAtlasParcellationSelector = createSelector( - state => state[viewerStateHelperStoreName], - state => state['viewerState'], - (viewerHelperState, viewerState) => { - const { selectedAtlasId, fetchedAtlases } = viewerHelperState - const { fetchedTemplates } = viewerState - - const allParcellations = fetchedTemplates.reduce(flattenFetchedTemplatesIntoParcellationsReducer, []) - - const selectedAtlas = selectedAtlasId && fetchedAtlases.find(a => a['@id'] === selectedAtlasId) - const atlasLayers = selectedAtlas?.parcellations - .map(p => { - const otherHalfOfParc = allParcellations.find(parc => parc['@id'] === p['@id']) || {} - return { - ...p, - ...otherHalfOfParc, - } - }) - return atlasLayers - } -) - -export const viewerStateAtlasLatestParcellationSelector = createSelector( - viewerStateAtlasParcellationSelector, - parcs => (parcs && parcs.filter( p => !p['@version'] || !p['@version']['@next']) || []) -) - -export const viewerStateParcVersionSelector = createSelector( - viewerStateAtlasParcellationSelector, - state => state['viewerState'], - (allAtlasParcellations, viewerState) => { - if (!viewerState || !viewerState.parcellationSelected) return [] - const returnParc = [] - const foundParc = allAtlasParcellations.find(p => p['@id'] === viewerState.parcellationSelected['@id']) - if (!foundParc) return [] - returnParc.push(foundParc) - const traverseParc = parc => { - if (!parc) return [] - if (!parc['@version']) return [] - if (parc['@version']['@next']) { - const nextParc = allAtlasParcellations.find(p => p['@id'] === parc['@version']['@next']) - if (nextParc) { - const nextParcAlreadyIncluded = returnParc.find(p => p['@id'] === nextParc['@id']) - if (!nextParcAlreadyIncluded) { - returnParc.unshift(nextParc) - traverseParc(nextParc) - } - } - } - - if (parc['@version']['@previous']) { - const previousParc = allAtlasParcellations.find(p => p['@id'] === parc['@version']['@previous']) - if (previousParc) { - const previousParcAlreadyIncluded = returnParc.find(p => p['@id'] === previousParc['@id']) - if (!previousParcAlreadyIncluded) { - returnParc.push(previousParc) - traverseParc(previousParc) - } - } - } - } - traverseParc(foundParc) - return returnParc - } -) - - -export const viewerStateSelectedTemplateFullInfoSelector = createSelector( - viewerStateGetSelectedAtlas, - viewerStateFetchedTemplatesSelector, - (selectedAtlas, fetchedTemplates) => { - if (!selectedAtlas) return null - const { templateSpaces } = selectedAtlas - return templateSpaces.map(templateSpace => { - const fullTemplateInfo = fetchedTemplates.find(t => t['@id'] === templateSpace['@id']) - return { - ...templateSpace, - ...(fullTemplateInfo || {}), - darktheme: (fullTemplateInfo || {}).useTheme === 'dark' - } - }) - } -) - -export const viewerStateContextedSelectedRegionsSelector = createSelector( - viewerStateSelectedRegionsSelector, - viewerStateGetSelectedAtlas, - viewerStateSelectedTemplatePureSelector, - viewerStateSelectedParcellationSelector, - (regions, atlas, template, parcellation) => regions.map(r => { - return { - ...r, - context: { - atlas, - template, - parcellation - } - } - }) -) diff --git a/src/services/stateStore.helper.ts b/src/services/stateStore.helper.ts deleted file mode 100644 index 172c58258dec163d118529afe22aeb3ef4af1ce4..0000000000000000000000000000000000000000 --- a/src/services/stateStore.helper.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createAction, props } from "@ngrx/store"; - -export const GENERAL_ACTION_TYPES = { - APPLY_STATE: 'APPLY_STATE', -} - -export const generalApplyState = createAction( - GENERAL_ACTION_TYPES.APPLY_STATE, - props<{ state: any }>() -) - -export const generalActionError = createAction( - `[generalActionError]`, - props<{ message: string }>() -) diff --git a/src/services/stateStore.service.spec.ts b/src/services/stateStore.service.spec.ts deleted file mode 100644 index 1fb8e0058c9d33c10a4fa249d435e9f84c84f7ce..0000000000000000000000000000000000000000 --- a/src/services/stateStore.service.spec.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { getMultiNgIdsRegionsLabelIndexMap } from './stateStore.service' - -const getRandomDummyData = () => Math.round(Math.random() * 1000).toString(16) - -const region1 = { - name: 'region 1', - labelIndex: 15, - ngId: 'left', - dummydata: getRandomDummyData() -} -const region2 = { - name: 'region 2', - labelIndex: 16, - ngId: 'right', - dummydata: getRandomDummyData() -} -const region3 = { - name: 'region 3', - labelIndex: 17, - ngId: 'right', - dummydata: getRandomDummyData() -} - -const dummyParcellationWithNgId = { - name: 'dummy parcellation name', - regions: [ - region1, - region2, - region3 - ] -} - -const dummyParcellationWithoutNgId = { - name: 'dummy parcellation name', - ngId: 'toplevel', - regions: [ - region1, - region2, - region3 - ].map(({ ngId, ...rest }) => { - return { - ...rest, - ...(ngId === 'left' ? { ngId } : {}) - } - }) -} - -describe('stateStore.service.ts', () => { - describe('getMultiNgIdsRegionsLabelIndexMap', () => { - describe('should not mutate original regions', () => { - - }) - - describe('should populate map properly', () => { - const map = getMultiNgIdsRegionsLabelIndexMap(dummyParcellationWithNgId) - it('populated map should have 2 top level', () => { - expect(map.size).toBe(2) - }) - - it('should container left and right top level', () => { - expect(map.get('left')).toBeTruthy() - expect(map.get('right')).toBeTruthy() - }) - - it('left top level should have 1 member', () => { - const leftMap = map.get('left') - expect(leftMap.size).toBe(1) - }) - - it('left top level should map 15 => region1', () => { - const leftMap = map.get('left') - expect(leftMap.get(15)).toEqual(region1) - }) - - it('right top level should have 2 member', () => { - const rightMap = map.get('right') - expect(rightMap.size).toBe(2) - }) - - it('right top level should map 16 => region2, 17 => region3', () => { - const rightMap = map.get('right') - expect(rightMap.get(16)).toEqual(region2) - expect(rightMap.get(17)).toEqual(region3) - }) - }) - - describe('should allow inheritance of ngId', () => { - - const map = getMultiNgIdsRegionsLabelIndexMap(dummyParcellationWithoutNgId) - it('populated map should have 2 top level', () => { - expect(map.size).toBe(2) - }) - - it('should container left and right top level', () => { - expect(map.get('left')).toBeTruthy() - expect(map.get('toplevel')).toBeTruthy() - }) - - it('left top level should have 1 member', () => { - const leftMap = map.get('left') - expect(leftMap.size).toBe(1) - }) - - it('left top level should map 15 => region1', () => { - const leftMap = map.get('left') - expect(leftMap.get(15)).toEqual(region1) - }) - - it('toplevel top level should have 2 member', () => { - const toplevelMap = map.get('toplevel') - expect(toplevelMap.size).toBe(2) - }) - - it('toplevel top level should map 16 => region2, 17 => region3', () => { - const toplevelMap = map.get('toplevel') - expect(toplevelMap.get(16).dummydata).toEqual(region2.dummydata) - expect(toplevelMap.get(17).dummydata).toEqual(region3.dummydata) - }) - }) - - describe('should allow inheritance of attr when specified', () => { - const attr = { - dummyattr: 'default dummy attr' - } - const map = getMultiNgIdsRegionsLabelIndexMap({ - ...dummyParcellationWithNgId, - dummyattr: 'p dummy attr' - }, attr) - it('every region should have dummyattr set properly', () => { - let regions = [] - for (const [ _key1, mMap ] of Array.from(map)) { - for (const [_key2, rs] of Array.from(mMap)) { - regions = [...regions, rs] - } - } - - for (const r of regions) { - expect(r.dummyattr).toEqual('p dummy attr') - } - }) - - }) - }) -}) \ No newline at end of file diff --git a/src/services/stateStore.service.ts b/src/services/stateStore.service.ts deleted file mode 100644 index b9d57ed8c8b50e314553449914360265b99178e2..0000000000000000000000000000000000000000 --- a/src/services/stateStore.service.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { filter } from 'rxjs/operators'; - -export { - recursiveFindRegionWithLabelIndexId -} from 'src/util/fn' - -export { getNgIds } from 'src/util/fn' - -import { - ActionInterface as NgViewerActionInterface, - defaultState as ngViewerDefaultState, - StateInterface as NgViewerStateInterface, - stateStore as ngViewerState, -} from './state/ngViewerState.store' -import { - defaultState as pluginDefaultState, - StateInterface as PluginStateInterface, - stateStore as pluginState, -} from './state/pluginState.store' -import { - ActionInterface as UIActionInterface, - defaultState as uiDefaultState, - IUiState, - stateStore as uiState, -} from './state/uiState.store' -import { - ACTION_TYPES as USER_CONFIG_ACTION_TYPES, - defaultState as userConfigDefaultState, - StateInterface as UserConfigStateInterface, - userConfigReducer as userConfigState, -} from './state/userConfigState.store' -import { - defaultState as viewerConfigDefaultState, - StateInterface as ViewerConfigStateInterface, - stateStore as viewerConfigState, -} from './state/viewerConfig.store' -import { - ActionInterface as ViewerActionInterface, - defaultState as viewerDefaultState, - StateInterface as ViewerStateInterface, - stateStore as viewerState, -} from './state/viewerState.store' - -import { - defaultState as defaultViewerHelperState, - viewerStateHelperStoreName -} from './state/viewerState.store.helper' - -export { pluginState } -export { viewerConfigState } -export { NgViewerStateInterface, NgViewerActionInterface, ngViewerState } -export { ViewerStateInterface, ViewerActionInterface, viewerState } -export { IUiState, UIActionInterface, uiState } -export { userConfigState, USER_CONFIG_ACTION_TYPES} - -export { CHANGE_NAVIGATION, DESELECT_LANDMARKS, FETCHED_TEMPLATE, SELECT_PARCELLATION, SELECT_REGIONS, USER_LANDMARKS } from './state/viewerState.store' -export { IDataEntry, IParcellationRegion, FETCHED_DATAENTRIES, FETCHED_SPATIAL_DATA, ILandmark, IOtherLandmarkGeometry, IPlaneLandmarkGeometry, IPointLandmarkGeometry, IProperty, IPublication, IReferenceSpace, IFile, IFileSupplementData } from './state/dataStore.store' -export { CLOSE_SIDE_PANEL, MOUSE_OVER_SEGMENT, OPEN_SIDE_PANEL, COLLAPSE_SIDE_PANEL_CURRENT_VIEW, EXPAND_SIDE_PANEL_CURRENT_VIEW } from './state/uiState.store' -export { UserConfigStateUseEffect } from './state/userConfigState.store' - -export { GENERAL_ACTION_TYPES, generalActionError } from './stateStore.helper' - -// TODO deprecate -export function safeFilter(key: string) { - return filter((state: any) => - (typeof state !== 'undefined' && state !== null) && - typeof state[key] !== 'undefined' && state[key] !== null) -} - -export function getMultiNgIdsRegionsLabelIndexMap(parcellation: any = {}, inheritAttrsOpt: any = { ngId: 'root' }): Map<string, Map<number, any>> { - const map: Map<string, Map<number, any>> = new Map() - - const inheritAttrs = Object.keys(inheritAttrsOpt) - if (inheritAttrs.indexOf('children') >=0 ) throw new Error(`children attr cannot be inherited`) - - const processRegion = (region: any) => { - const { ngId: rNgId } = region - const existingMap = map.get(rNgId) - const labelIndex = Number(region.labelIndex) - if (labelIndex) { - if (!existingMap) { - const newMap = new Map() - newMap.set(labelIndex, region) - map.set(rNgId, newMap) - } else { - existingMap.set(labelIndex, region) - } - } - - if (region.children && Array.isArray(region.children)) { - for (const r of region.children) { - const copiedRegion = { ...r } - for (const attr of inheritAttrs){ - copiedRegion[attr] = copiedRegion[attr] || region[attr] || parcellation[attr] - } - processRegion(copiedRegion) - } - } - } - - if (!parcellation) throw new Error(`parcellation needs to be defined`) - if (!parcellation.regions) throw new Error(`parcellation.regions needs to be defined`) - if (!Array.isArray(parcellation.regions)) throw new Error(`parcellation.regions needs to be an array`) - - for (const region of parcellation.regions){ - const copiedregion = { ...region } - for (const attr of inheritAttrs){ - copiedregion[attr] = copiedregion[attr] || parcellation[attr] - } - processRegion(copiedregion) - } - - return map -} - -/** - * labelIndexMap maps label index to region - * @TODO deprecate - */ -export function getLabelIndexMap(regions: any[]): Map<number, any> { - const returnMap = new Map() - - const reduceRegions = (rs: any[]) => { - rs.forEach(region => { - if ( region.labelIndex ) { returnMap.set(Number(region.labelIndex), - Object.assign({}, region, {labelIndex : Number(region.labelIndex)})) - } - if ( region.children && region.children.constructor === Array ) { reduceRegions(region.children) } - }) - } - - if (regions && regions.forEach) { reduceRegions(regions) } - return returnMap -} - -/** - * - * @param regions regions to deep iterate to find all ngId 's, filtering out falsy values - * n.b. returns non unique list - */ -export interface DedicatedViewState { - dedicatedView: string | null -} - -// @TODO deprecate -export function isDefined(obj) { - return typeof obj !== 'undefined' && obj !== null -} - -export interface IavRootStoreInterface { - pluginState: PluginStateInterface - viewerConfigState: ViewerConfigStateInterface - ngViewerState: NgViewerStateInterface - viewerState: ViewerStateInterface - dataStore: any - uiState: IUiState - userConfigState: UserConfigStateInterface -} - -export const defaultRootState: any = { - pluginState: pluginDefaultState, - dataStore: {}, - ngViewerState: ngViewerDefaultState, - uiState: uiDefaultState, - userConfigState: userConfigDefaultState, - viewerConfigState: viewerConfigDefaultState, - viewerState: viewerDefaultState, - [viewerStateHelperStoreName]: defaultViewerHelperState -} diff --git a/src/services/templateCoordinatesTransformation.service.spec.ts b/src/services/templateCoordinatesTransformation.service.spec.ts deleted file mode 100644 index 0aa477d103f4dfe8b0894314e4a08bfde4666cfb..0000000000000000000000000000000000000000 --- a/src/services/templateCoordinatesTransformation.service.spec.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { TemplateCoordinatesTransformation } from './templateCoordinatesTransformation.service' -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing' -import { TestBed, fakeAsync, tick } from '@angular/core/testing' - -describe('templateCoordinatesTransformation.service.spec.ts', () => { - describe('TemplateCoordinatesTransformation', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - HttpClientTestingModule - ], - providers: [ - TemplateCoordinatesTransformation - ] - }) - }) - - afterEach(() => { - const ctrl = TestBed.inject(HttpTestingController) - ctrl.verify() - }) - - describe('getPointCoordinatesForTemplate', () => { - it('should instantiate service properly', () => { - const service = TestBed.inject(TemplateCoordinatesTransformation) - expect(service).toBeTruthy() - expect(service.getPointCoordinatesForTemplate).toBeTruthy() - }) - - it('should transform argument properly', () => { - const service = TestBed.inject(TemplateCoordinatesTransformation) - const httpTestingController = TestBed.inject(HttpTestingController) - - // subscriptions are necessary for http fetch to occur - service.getPointCoordinatesForTemplate( - TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.MNI152, - TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, - [1,2,3] - ).subscribe((_ev) => { - - }) - const req = httpTestingController.expectOne(service.url) - expect(req.request.method).toEqual('POST') - expect( - JSON.parse(req.request.body) - ).toEqual({ - 'source_space': TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.MNI152, - 'target_space': TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, - 'source_points': [ - [1e-6, 2e-6, 3e-6] - ] - }) - req.flush({}) - }) - - it('transforms mapped space name(s)', () => { - const service = TestBed.inject(TemplateCoordinatesTransformation) - const httpTestingController = TestBed.inject(HttpTestingController) - - const key = Array.from(TemplateCoordinatesTransformation.NameMap.keys())[0] - service.getPointCoordinatesForTemplate( - key, - TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, - [1,2,3] - ).subscribe((_ev) => { - - }) - const req = httpTestingController.expectOne(service.url) - expect(req.request.method).toEqual('POST') - expect( - JSON.parse(req.request.body) - ).toEqual({ - 'source_space': TemplateCoordinatesTransformation.NameMap.get(key), - 'target_space': TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, - 'source_points': [ - [1e-6, 2e-6, 3e-6] - ] - }) - req.flush({}) - }) - - - it('should transform response properly', () => { - - const service = TestBed.inject(TemplateCoordinatesTransformation) - const httpTestingController = TestBed.inject(HttpTestingController) - - service.getPointCoordinatesForTemplate( - TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.MNI152, - TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, - [1,2,3] - ).subscribe(({ status, result }) => { - expect(status).toEqual('completed') - expect(result).toEqual([1e6, 2e6, 3e6]) - }) - const req = httpTestingController.expectOne(service.url) - req.flush({ - 'target_points':[ - [1, 2, 3] - ] - }) - }) - - it('if server returns >=400, fallback gracefully', () => { - const service = TestBed.inject(TemplateCoordinatesTransformation) - const httpTestingController = TestBed.inject(HttpTestingController) - - service.getPointCoordinatesForTemplate( - TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.MNI152, - TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, - [1,2,3] - ).subscribe(({ status }) => { - expect(status).toEqual('error') - }) - const req = httpTestingController.expectOne(service.url) - - req.flush('intercepted', { status: 500, statusText: 'internal server error' }) - }) - - it('if server does not respond after 3s, fallback gracefully', fakeAsync(() => { - - const service = TestBed.inject(TemplateCoordinatesTransformation) - const httpTestingController = TestBed.inject(HttpTestingController) - - service.getPointCoordinatesForTemplate( - TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.MNI152, - TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN, - [1,2,3] - ).subscribe(({ status, statusText }) => { - expect(status).toEqual('error') - expect(statusText).toEqual(`Timeout after 3s`) - }) - const req = httpTestingController.expectOne(service.url) - tick(4000) - expect(req.cancelled).toBe(true) - })) - }) - }) -}) diff --git a/src/services/templateCoordinatesTransformation.service.ts b/src/services/templateCoordinatesTransformation.service.ts deleted file mode 100644 index c9fcf3a9d5b191ecf9acc3b68890f06cfceb549c..0000000000000000000000000000000000000000 --- a/src/services/templateCoordinatesTransformation.service.ts +++ /dev/null @@ -1,77 +0,0 @@ -import {Injectable} from "@angular/core"; -import {HttpClient, HttpHeaders, HttpErrorResponse} from "@angular/common/http"; -import { catchError, timeout, map } from "rxjs/operators"; -import { of, Observable } from "rxjs"; -import { environment } from 'src/environments/environment' - -export interface ITemplateCoordXformResp{ - status: 'pending' | 'error' | 'completed' - statusText?: string - result? : [number, number, number] -} - -@Injectable({ - providedIn: 'root', -}) -export class TemplateCoordinatesTransformation { - - static VALID_TEMPLATE_SPACE_NAMES = { - MNI152: 'MNI 152 ICBM 2009c Nonlinear Asymmetric', - COLIN27: 'MNI Colin 27', - BIG_BRAIN: 'Big Brain (Histology)', - INFANT: 'Infant Atlas', - } - - static NameMap = new Map([ - ['MNI152 2009c nonl asym', TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.MNI152], - ["Big Brain", TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES.BIG_BRAIN] - ]) - - constructor(private httpClient: HttpClient) {} - - public url = `${environment.SPATIAL_TRANSFORM_BACKEND.replace(/\/$/, '')}/v1/transform-points` - - // jasmine marble cannot test promise properly - // see https://github.com/ngrx/platform/issues/498#issuecomment-337465179 - // in order to properly test with marble, use obs instead of promise - getPointCoordinatesForTemplate(sourceTemplateName: string, targetTemplateName: string, coordinatesInNm: [number, number, number]): Observable<ITemplateCoordXformResp> { - const httpOptions = { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - } - const srcTmplName = Object.values(TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES).includes(sourceTemplateName) - ? sourceTemplateName - : TemplateCoordinatesTransformation.NameMap.get(sourceTemplateName) - - const targetTmplName = Object.values(TemplateCoordinatesTransformation.VALID_TEMPLATE_SPACE_NAMES).includes(targetTemplateName) - ? targetTemplateName - : TemplateCoordinatesTransformation.NameMap.get(targetTemplateName) - - return this.httpClient.post( - this.url, - JSON.stringify({ - 'source_points': [[...coordinatesInNm.map(c => c/1e6)]], - 'source_space': srcTmplName, - 'target_space': targetTmplName - }), - httpOptions - ).pipe( - map(resp => { - return { - status: 'completed', - result: resp['target_points'][0].map((r: number)=> r * 1e6) - } as ITemplateCoordXformResp - }), - catchError(err => { - if (err instanceof HttpErrorResponse) { - return of(({ status: 'error', statusText: err.message } as ITemplateCoordXformResp)) - } else { - return of(({ status: 'error', statusText: err.toString() } as ITemplateCoordXformResp)) - } - }), - timeout(3000), - catchError(() => of(({ status: 'error', statusText: `Timeout after 3s` } as ITemplateCoordXformResp))), - ) - } -} \ No newline at end of file diff --git a/src/share/share.directive.ts b/src/share/share.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..e3200cba6816e4182048d4b629f9f9c6e5fe56ef --- /dev/null +++ b/src/share/share.directive.ts @@ -0,0 +1,18 @@ +import { Directive, HostListener } from "@angular/core"; +import { MatBottomSheet } from "@angular/material/bottom-sheet"; +import { ShareSheetComponent } from "./shareSheet/shareSheet.component" + + +@Directive({ + selector: `[sxplr-share-view]` +}) + +export class ShareDirective{ + constructor(private btmSht: MatBottomSheet){ + + } + @HostListener('click') + onClick(){ + this.btmSht.open(ShareSheetComponent) + } +} diff --git a/src/share/share.module.ts b/src/share/share.module.ts index 9634e12279dd09f165c6a6d4fd34653cbf52550f..f3137eaadc83a3aaead101efe1b76cc600301409 100644 --- a/src/share/share.module.ts +++ b/src/share/share.module.ts @@ -6,6 +6,8 @@ import { SaneUrl } from "./saneUrl/saneUrl.component"; import { CommonModule } from "@angular/common"; import { ReactiveFormsModule, FormsModule } from "@angular/forms"; import { AuthModule } from "src/auth"; +import { ShareSheetComponent } from "./shareSheet/shareSheet.component"; +import { ShareDirective } from "./share.directive"; @NgModule({ imports: [ @@ -19,10 +21,14 @@ import { AuthModule } from "src/auth"; declarations: [ ClipboardCopy, SaneUrl, + ShareSheetComponent, + ShareDirective, ], exports: [ ClipboardCopy, SaneUrl, + ShareSheetComponent, + ShareDirective, ] }) diff --git a/src/share/shareSheet/shareSheet.component.ts b/src/share/shareSheet/shareSheet.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d4b9f963ebafc0b83850010bf8b18e70f7d6a15 --- /dev/null +++ b/src/share/shareSheet/shareSheet.component.ts @@ -0,0 +1,24 @@ +import { Component } from "@angular/core"; +import { MatDialog } from "@angular/material/dialog"; +import { ARIA_LABELS } from 'common/constants' +import { SaneUrl } from "../saneUrl/saneUrl.component" + +@Component({ + selector: 'sxplr-share-sheet', + templateUrl: './shareSheet.template.html', + styleUrls: [ + `./shareSheet.style.css` + ] +}) + +export class ShareSheetComponent{ + public ARIA_LABELS = ARIA_LABELS + + constructor(private dialog: MatDialog){ + + } + + openShareSaneUrl(){ + this.dialog.open(SaneUrl, { ariaLabel: ARIA_LABELS.SHARE_CUSTOM_URL }) + } +} diff --git a/src/share/shareSheet/shareSheet.style.css b/src/share/shareSheet/shareSheet.style.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/share/shareSheet/shareSheet.template.html b/src/share/shareSheet/shareSheet.template.html new file mode 100644 index 0000000000000000000000000000000000000000..cea5242302cf57ff1cc718ac1b2d98b7a9d79f0c --- /dev/null +++ b/src/share/shareSheet/shareSheet.template.html @@ -0,0 +1,34 @@ +<h4 class="mat-h4"> + Share via +</h4> +<mat-nav-list> + <mat-list-item iav-clipboard-copy + [attr.aria-label]="ARIA_LABELS.SHARE_COPY_URL_CLIPBOARD" + [attr.tab-index]="10"> + <mat-icon + class="mr-4" + fontSet="fas" + fontIcon="fa-copy"> + </mat-icon> + <span> + Copy link to this view + </span> + </mat-list-item> + <mat-list-item + [attr.aria-label]="ARIA_LABELS.SHARE_CUSTOM_URL" + [attr.tab-index]="10" + (click)="openShareSaneUrl()" + [matTooltip]="ARIA_LABELS.SHARE_CUSTOM_URL" + > + <mat-icon + class="mr-4" + fontSet="fas" + fontIcon="fa-link"> + </mat-icon> + + <span> + Create custom URL + </span> + + </mat-list-item> +</mat-nav-list> diff --git a/src/sharedModules/angularMaterial.module.ts b/src/sharedModules/angularMaterial.module.ts index d5384113b07877cc3d0c0a726387c2c38d5e64bf..6f29b8913a23db32f47c96f936d0d043dde498e6 100644 --- a/src/sharedModules/angularMaterial.module.ts +++ b/src/sharedModules/angularMaterial.module.ts @@ -28,11 +28,16 @@ import {MatMenuModule} from "@angular/material/menu"; import { MatToolbarModule } from '@angular/material/toolbar' import { ClipboardModule } from '@angular/cdk/clipboard' +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import {MatProgressBarModule} from "@angular/material/progress-bar"; +import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; const defaultDialogOption: MatDialogConfig = new MatDialogConfig() @NgModule({ imports: [ + BrowserAnimationsModule, + MatButtonModule, MatSnackBarModule, MatCheckboxModule, @@ -60,6 +65,8 @@ const defaultDialogOption: MatDialogConfig = new MatDialogConfig() ScrollingModule, MatToolbarModule, ClipboardModule, + MatProgressBarModule, + MatProgressSpinnerModule, ], exports: [ MatButtonModule, @@ -89,12 +96,13 @@ const defaultDialogOption: MatDialogConfig = new MatDialogConfig() ScrollingModule, MatToolbarModule, ClipboardModule, + MatProgressBarModule, + MatProgressSpinnerModule, ], providers: [{ provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { ...defaultDialogOption, - panelClass: 'iav-dialog-class', }, }], }) diff --git a/src/spotlight/spot-light.module.ts b/src/spotlight/spot-light.module.ts index 6ae55158469a7ac8a575697a4e477d12de6a352e..a485e0d23622a2d4637334154cc6d6c0b9207c97 100644 --- a/src/spotlight/spot-light.module.ts +++ b/src/spotlight/spot-light.module.ts @@ -15,11 +15,8 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; BrowserAnimationsModule, CommonModule ], - exports:[ + exports: [ SlSpotlightDirective - ], - entryComponents: [ - SpotlightBackdropComponent ] }) export class SpotLightModule { } diff --git a/src/state/actions.ts b/src/state/actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b29757e099d292ee870c3115a26e906a814b7d2 --- /dev/null +++ b/src/state/actions.ts @@ -0,0 +1,16 @@ +import { createAction, props } from "@ngrx/store"; +import { MainState, nameSpace } from "./const" + +export const generalActionError = createAction( + `${nameSpace} generalActionError`, + props<{ + message: string + }>() +) + +export const generalApplyState = createAction( + `${nameSpace} generalApplyState`, + props<{ + state: MainState + }>() +) diff --git a/src/state/annotations/actions.ts b/src/state/annotations/actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..8527aaaf3ee8c8271868a8844113f52799ad9bdf --- /dev/null +++ b/src/state/annotations/actions.ts @@ -0,0 +1,21 @@ +import { createAction, props } from "@ngrx/store" +import { nameSpace } from "./const" +import { UnionAnnotation } from "./store" + +export const clearAllAnnotations = createAction( + `${nameSpace} clearAllAnnotations` +) + +export const rmAnnotations = createAction( + `${nameSpace} rmAnnotations`, + props<{ + annotations: {'@id': string }[] + }>() +) + +export const addAnnotations = createAction( + `${nameSpace} addAnnotations`, + props<{ + annotations: UnionAnnotation[] + }>() +) diff --git a/src/state/annotations/const.ts b/src/state/annotations/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..0ca3a95036366f410633ade16db8d2a4e1665064 --- /dev/null +++ b/src/state/annotations/const.ts @@ -0,0 +1 @@ +export const nameSpace = `[state.annotations]` \ No newline at end of file diff --git a/src/state/annotations/index.ts b/src/state/annotations/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..13a9be8ec3d7ce59ed27481285119c496fce0f63 --- /dev/null +++ b/src/state/annotations/index.ts @@ -0,0 +1,4 @@ +export * as actions from "./actions" +export { Annotation, AnnotationState, reducer, defaultState, TypesOfDetailedAnnotations, UnionAnnotation, AnnotationColor } from "./store" +export { nameSpace } from "./const" +export * as selectors from "./selectors" diff --git a/src/state/annotations/selectors.ts b/src/state/annotations/selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..5504a6bb1fb7ab03a4053a365c24d55290a50036 --- /dev/null +++ b/src/state/annotations/selectors.ts @@ -0,0 +1,32 @@ +import { createSelector } from "@ngrx/store" +import { nameSpace } from "./const" +import { Annotation, AnnotationState } from "./store" +import { selectors as atlasSelectionSelectors } from "../atlasSelection" + +const selectStore = state => state[nameSpace] as AnnotationState + +export const annotations = createSelector( + selectStore, + state => state.annotations +) + +export const spaceFilteredAnnotations = createSelector( + selectStore, + atlasSelectionSelectors.selectStore, + (annState, atlasSelState) => annState.annotations.filter(ann => { + const spaceId = atlasSelState.selectedTemplate['@id'] + if (ann['openminds']) { + return (ann as Annotation<'openminds'>).openminds.coordinateSpace['@id'] === spaceId + } + + if (ann['line']) { + return (ann as Annotation<'line'>).line.pointA.coordinateSpace['@id'] === spaceId + && (ann as Annotation<'line'>).line.pointB.coordinateSpace['@id'] === spaceId + } + + if (ann['box']) { + return (ann as Annotation<'box'>).box.pointA.coordinateSpace['@id'] === spaceId + && (ann as Annotation<'box'>).box.pointB.coordinateSpace['@id'] === spaceId + } + }) +) diff --git a/src/state/annotations/store.ts b/src/state/annotations/store.ts new file mode 100644 index 0000000000000000000000000000000000000000..21198ff816f5315721746a0cbeba8d8f55c90d47 --- /dev/null +++ b/src/state/annotations/store.ts @@ -0,0 +1,72 @@ +import { createReducer, on } from "@ngrx/store" +import { OpenMINDSCoordinatePoint } from "src/atlasComponents/sapi" +import * as actions from "./actions" + +type Line = { + pointA: OpenMINDSCoordinatePoint + pointB: OpenMINDSCoordinatePoint +} + +type BBox = { + pointA: OpenMINDSCoordinatePoint + pointB: OpenMINDSCoordinatePoint +} + +export type TypesOfDetailedAnnotations = { + openminds: OpenMINDSCoordinatePoint + line: Line + box: BBox +} + +export enum AnnotationColor { + WHITE="WHITE", + RED="RED", + BLUE="BLUE", +} + +export type Annotation<T extends keyof TypesOfDetailedAnnotations> = { + "@id": string + name: string + description?: string + color?: AnnotationColor +} & { [key in T] : TypesOfDetailedAnnotations[T]} + +export type UnionAnnotation = Annotation<'openminds'> | Annotation<'line'> | Annotation<'box'> + +export type AnnotationState = { + annotations: UnionAnnotation[] +} + +export const defaultState: AnnotationState = { + annotations: [] +} + +const reducer = createReducer( + defaultState, + on( + actions.addAnnotations, + (state, { annotations }) => { + return { + ...state, + annotations: [ + ...state.annotations, + ...annotations, + ] + } + } + ), + on( + actions.rmAnnotations, + (state, { annotations }) => { + const annIdToBeRemoved = annotations.map(ann => ann["@id"]) + return { + ...state, + annotations: state.annotations.filter(ann => !annIdToBeRemoved.includes(ann["@id"]) ) + } + } + ) +) + +export { + reducer +} diff --git a/src/state/atlasAppearance/action.ts b/src/state/atlasAppearance/action.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a4ce26e148d801c726ba23c3eee906e736aa510 --- /dev/null +++ b/src/state/atlasAppearance/action.ts @@ -0,0 +1,30 @@ +import { createAction, props } from "@ngrx/store"; +import { CustomLayer, nameSpace } from "./const" + +export const setOctantRemoval = createAction( + `${nameSpace} setOctantRemoval`, + props<{ + flag: boolean + }>() +) + +export const setShowDelineation = createAction( + `${nameSpace} setShowDelineation`, + props<{ + flag: boolean + }>() +) + +export const addCustomLayer = createAction( + `${nameSpace} addCustomLayer`, + props<{ + customLayer: CustomLayer + }>() +) + +export const removeCustomLayer = createAction( + `${nameSpace} removeCustomLayer`, + props<{ + id: string + }>() +) diff --git a/src/state/atlasAppearance/const.ts b/src/state/atlasAppearance/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6c24f31b6ffc7e8bebf9647f9b0b4f538c0f710 --- /dev/null +++ b/src/state/atlasAppearance/const.ts @@ -0,0 +1,52 @@ +import { SapiRegionModel } from "src/atlasComponents/sapi" +export const nameSpace = `[state.atlasAppearance]` + +type CustomLayerBase = { + id: string +} + +export type ColorMapCustomLayer = { + clType: 'customlayer/colormap' | 'baselayer/colormap' + colormap: WeakMap<SapiRegionModel, number[]> +} & CustomLayerBase + +export type ThreeSurferCustomLayer = { + clType: 'baselayer/threesurfer' + source: string + laterality: 'left' | 'right' + name: string +} & CustomLayerBase + +export type ThreeSurferCustomLabelLayer = { + clType: 'baselayer/threesurfer-label' + source: string + laterality: 'left' | 'right' +} & CustomLayerBase + +export type NgLayerCustomLayer = { + clType: 'customlayer/nglayer' | 'baselayer/nglayer' + source: string + visible?: boolean + shader?: string + transform?: number[][] + opacity?: number + segments?: (number|string)[] + // type?: string + + // annotation?: string // TODO what is this used for? +} & CustomLayerBase + +/** + * custom layer is a catch all term that apply **any** special looks + * to an atlas. it could include: + * + * - different colormap + * - different volume (pmap) + * - different indicies + * + * It is up to the viewer on how to interprete these information. + * each instance **must** contain a clType and an id + * - clType facilitates viewer on how to interprete the custom layer + * - id allows custom layer to be removed, if necessary + */ +export type CustomLayer = ColorMapCustomLayer | NgLayerCustomLayer | ThreeSurferCustomLayer | ThreeSurferCustomLabelLayer diff --git a/src/state/atlasAppearance/index.ts b/src/state/atlasAppearance/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..739f036f65baf71930eff7ae9fd37067cfb4e012 --- /dev/null +++ b/src/state/atlasAppearance/index.ts @@ -0,0 +1,4 @@ +export * as actions from "./action" +export * as selectors from "./selector" +export { nameSpace, ColorMapCustomLayer, CustomLayer, NgLayerCustomLayer } from "./const" +export { reducer, AtlasAppearanceStore, defaultState } from "./store" diff --git a/src/state/atlasAppearance/selector.ts b/src/state/atlasAppearance/selector.ts new file mode 100644 index 0000000000000000000000000000000000000000..b7eac7bc1701c6e490a6a72ccd9cf8412d4e7aeb --- /dev/null +++ b/src/state/atlasAppearance/selector.ts @@ -0,0 +1,20 @@ +import { createSelector } from "@ngrx/store" +import { nameSpace } from "./const" +import { AtlasAppearanceStore } from "./store" + +const selectStore = state => state[nameSpace] as AtlasAppearanceStore + +export const octantRemoval = createSelector( + selectStore, + state => state.octantRemoval +) + +export const showDelineation = createSelector( + selectStore, + state => state.showDelineation +) + +export const customLayers = createSelector( + selectStore, + state => state.customLayers +) diff --git a/src/state/atlasAppearance/store.ts b/src/state/atlasAppearance/store.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c060da26b4696af0865d0f5c545fdc0d42cffd4 --- /dev/null +++ b/src/state/atlasAppearance/store.ts @@ -0,0 +1,62 @@ +import { createReducer, on } from "@ngrx/store" +import * as actions from "./action" +import { CustomLayer } from "./const" + +export type AtlasAppearanceStore = { + + octantRemoval: boolean + showDelineation: boolean + customLayers: CustomLayer[] +} + +export const defaultState: AtlasAppearanceStore = { + octantRemoval: true, + showDelineation: true, + customLayers: [] +} + +export const reducer = createReducer( + defaultState, + on( + actions.setOctantRemoval, + (state, { flag }) => { + return { + ...state, + octantRemoval: flag + } + } + ), + on( + actions.setShowDelineation, + (state, { flag }) => { + return { + ...state, + showDelineation: flag + } + } + ), + on( + actions.addCustomLayer, + (state, { customLayer }) => { + const { customLayers } = state + + return { + ...state, + customLayers: [ + customLayer, + ...customLayers.filter(l => l.id !== customLayer.id) + ] + } + } + ), + on( + actions.removeCustomLayer, + (state, { id }) => { + const { customLayers } = state + return { + ...state, + customLayers: customLayers.filter(l => l.id !== id) + } + } + ) +) diff --git a/src/state/atlasSelection/actions.ts b/src/state/atlasSelection/actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..8787234b909287c331473dfe6d0cceb578375111 --- /dev/null +++ b/src/state/atlasSelection/actions.ts @@ -0,0 +1,181 @@ +import { createAction, props } from "@ngrx/store"; +import { SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; +import { BreadCrumb, nameSpace, ViewerMode, AtlasSelectionState } from "./const" + +export const selectAtlas = createAction( + `${nameSpace} selectAtlas`, + props<{ + atlas: SapiAtlasModel + }>() +) + +export const selectTemplate = createAction( + `${nameSpace} selectTemplate`, + props<{ + template: SapiSpaceModel + }>() +) + +export const selectParcellation = createAction( + `${nameSpace} selectParcellation`, + props<{ + parcellation: SapiParcellationModel + }>() +) + +/** + * setAtlasSelectionState is called as a final step to (potentially) set: + * - selectedAtlas + * - selectedTemplate + * - selectedParcellation + * + * It is up to the dispatcher to ensure that the selection makes sense. + * If any field is unset, it will take the default value from the store. + * It is **specifically** not setup to do **anymore** than atlas, template and parcellation + * + * We may setup post hook for navigation adjustments/etc. + * Probably easier is simply subscribe to store and react to selectedTemplate selector + */ +export const setAtlasSelectionState = createAction( + `${nameSpace} setAtlasSelectionState`, + props<Partial<AtlasSelectionState>>() +) + +export const setSelectedParcellationAllRegions = createAction( + `${nameSpace} setSelectedParcellationAllRegions`, + props<{ + regions: SapiRegionModel[] + }>() +) + +export const selectRegion = createAction( + `${nameSpace} selectRegion`, + props<{ + region: SapiRegionModel + }>() +) + +export const setSelectedRegions = createAction( + `${nameSpace} setSelectedRegions`, + props<{ + regions: SapiRegionModel[] + }>() +) + +export const setStandAloneVolumes = createAction( + `${nameSpace} setStandAloneVolumes`, + props<{ + standAloneVolumes: string[] + }>() +) + +export const setNavigation = createAction( + `${nameSpace} setNavigation`, + props<{ + navigation: { + position: number[] + orientation: number[] + zoom: number + perspectiveOrientation: number[] + perspectiveZoom: number + } + }>() +) + +export const setViewerMode = createAction( + `${nameSpace} setViewerMode`, + props<{ + viewerMode: ViewerMode + }>() +) + +export const showBreadCrumb = createAction( + `${nameSpace} showBreadCrumb`, + props<{ + breadcrumb: BreadCrumb + }>() +) + +export const dismissBreadCrumb = createAction( + `${nameSpace} dismissBreadCrumb`, + props<{ + id: string + }>() +) + +export const clearSelectedRegions = createAction( + `${nameSpace} clearSelectedRegions` +) + +export const selectATPById = createAction( + `${nameSpace} selectATPById`, + props<{ + atlasId?: string + templateId?: string + parcellationId?: string + }>() +) + +export const clearNonBaseParcLayer = createAction( + `${nameSpace} clearNonBaseParcLayer` +) + +export const clearStandAloneVolumes = createAction( + `${nameSpace} clearStandAloneVolumes` +) + +/** + * n.b. position in nm! + * TODO this action currently sucks. + * it depends on nehuba being available + * and if so, potentially ease move there + * + * should be moved to nehuba/store/navigateTo instead + */ +export const navigateTo = createAction( + `${nameSpace} navigateTo`, + props<{ + navigation: Partial<{ + position: number[] + orientation: number[] + zoom: number + perspectiveOrientation: number[] + perspectiveZoom: number + }> + physical?: boolean + animation?: boolean + }>() +) + +export const navigateToRegion = createAction( + `${nameSpace} navigateToRegion`, + props<{ + region: SapiRegionModel + }>() +) + +export const clearViewerMode = createAction( + `${nameSpace} clearViewerMode`, +) + +export const toggleRegionSelect = createAction( + `${nameSpace} toggleRegionSelect`, + props<{ + region: SapiRegionModel + }>() +) + +export const toggleRegionSelectById = createAction( + `${nameSpace} toggleRegionSelectById`, + props<{ + id: string + }>() +) + +export const viewSelRegionInNewSpace = createAction( + `${nameSpace} viewSelRegionInNewSpace`, + props<{ + region: SapiRegionModel + template: SapiSpaceModel + }>() +) diff --git a/src/state/atlasSelection/const.ts b/src/state/atlasSelection/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..1499e2472a896352406389a50a6eacaa14752539 --- /dev/null +++ b/src/state/atlasSelection/const.ts @@ -0,0 +1,33 @@ +import { SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" + +export const nameSpace = `[state.atlasSelection]` +export type ViewerMode = 'annotating' | 'key frame' +export type BreadCrumb = { + id: string + name: string +} + +export type AtlasSelectionState = { + selectedAtlas: SapiAtlasModel + selectedTemplate: SapiSpaceModel + selectedParcellation: SapiParcellationModel + selectedParcellationAllRegions: SapiRegionModel[] + + selectedRegions: SapiRegionModel[] + standAloneVolumes: string[] + + /** + * the navigation may mean something very different + * depending on if the user is using threesurfer/nehuba view + */ + navigation: { + position: number[] + orientation: number[] + zoom: number + perspectiveOrientation: number[] + perspectiveZoom: number + } + + viewerMode: ViewerMode + breadcrumbs: BreadCrumb[] +} diff --git a/src/state/atlasSelection/effects.spec.ts b/src/state/atlasSelection/effects.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..556e002c56f518ba5957629a178c5249ad37d234 --- /dev/null +++ b/src/state/atlasSelection/effects.spec.ts @@ -0,0 +1,402 @@ +import { TestBed } from "@angular/core/testing" +import { provideMockActions } from "@ngrx/effects/testing" +import { Action } from "@ngrx/store" +import { MockStore, provideMockStore } from "@ngrx/store/testing" +import { hot } from "jasmine-marbles" +import { Observable, of, throwError } from "rxjs" +import { SAPI, SAPIModule, SapiRegionModel, SapiAtlasModel, SapiSpaceModel, SapiParcellationModel } from "src/atlasComponents/sapi" +import { IDS } from "src/atlasComponents/sapi/constants" +import { actions, selectors } from "." +import { Effect } from "./effects" +import * as mainActions from "../actions" +import { atlasSelection } from ".." + +describe("> effects.ts", () => { + describe("> Effect", () => { + + let actions$ = new Observable<Action>() + let hoc1left: SapiRegionModel + let hoc1leftCentroid: SapiRegionModel + let hoc1leftCentroidWrongSpc: SapiRegionModel + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [ + // HttpClientTestingModule, + SAPIModule + ], + providers: [ + Effect, + provideMockStore(), + provideMockActions(() => actions$) + ] + }) + + /** + * only need to populate hoc1 left once + */ + if (!hoc1left) { + + const sapisvc = TestBed.inject(SAPI) + const regions = await sapisvc.getParcRegions(IDS.ATLAES.HUMAN, IDS.PARCELLATION.JBA29, IDS.TEMPLATES.MNI152).toPromise() + hoc1left = regions.find(r => /hoc1/i.test(r.name) && /left/i.test(r.name)) + if (!hoc1left) throw new Error(`cannot find hoc1 left`) + hoc1leftCentroid = JSON.parse(JSON.stringify(hoc1left)) + hoc1leftCentroid.hasAnnotation.bestViewPoint = { + coordinateSpace: { + '@id': IDS.TEMPLATES.BIG_BRAIN + }, + coordinates: [{ + value: 1 + }, { + value: 2 + }, { + value: 3 + }] + } + hoc1leftCentroidWrongSpc = JSON.parse(JSON.stringify(hoc1leftCentroid)) + hoc1leftCentroidWrongSpc.hasAnnotation.bestViewPoint.coordinateSpace['@id'] = IDS.TEMPLATES.COLIN27 + } + }) + + it('> can be init', () => { + const effects = TestBed.inject(Effect) + expect(effects).toBeTruthy() + }) + + describe('> selectTemplate$', () => { + + describe('> when transiting from template A to template B', () => { + describe('> if the current navigation is correctly formed', () => { + it('> uses current navigation param', () => { + + }) + }) + + describe('> if current navigation is malformed', () => { + it('> if current navigation is undefined, use nehubaConfig of last template', () => { + }) + + it('> if current navigation is empty object, use nehubaConfig of last template', () => { + }) + }) + + }) + + it('> if coordXform returns error', () => { + + }) + + it('> if coordXform complete', () => { + + }) + + }) + + describe("> onTemplateParcSelectionPostHook", () => { + describe("> 0", () => { + }) + describe("> 1", () => { + const currNavigation = { + orientation: [0, 0, 0, 1], + perspectiveOrientation: [0, 0, 0, 1], + perspectiveZoom: 1, + position: [1, 2, 3], + zoom: 1 + } + beforeEach(() => { + const store = TestBed.inject(MockStore) + store.overrideSelector(atlasSelection.selectors.navigation, currNavigation) + }) + describe("> when atlas is different", () => { + describe("> if no atlas prior", () => { + + it("> navigation should be reset", () => { + const effects = TestBed.inject(Effect) + const hook = effects.onTemplateParcSelectionPostHook[1] + const obs = hook({ + current: { + atlas: null, + parcellation: null, + template: null + }, + previous: { + atlas: { + "@id": IDS.ATLAES.RAT + } as any, + parcellation: { + "@id": IDS.PARCELLATION.WAXHOLMV4 + } as any, + template: { + "@id": IDS.TEMPLATES.WAXHOLM + } as any, + } + }) + + expect(obs).toBeObservable( + hot('(a|)', { + a: { + navigation: null + } + }) + ) + }) + }) + describe("> if different atlas prior", () => { + + it("> navigation should be reset", () => { + const effects = TestBed.inject(Effect) + const hook = effects.onTemplateParcSelectionPostHook[1] + const obs = hook({ + current: { + atlas: { + "@id": IDS.ATLAES.HUMAN + } as any, + parcellation: { + "@id": IDS.PARCELLATION.JBA29 + } as any, + template: { + "@id": IDS.TEMPLATES.MNI152 + } as any, + }, + previous: { + atlas: { + "@id": IDS.ATLAES.RAT + } as any, + parcellation: { + "@id": IDS.PARCELLATION.WAXHOLMV4 + } as any, + template: { + "@id": IDS.TEMPLATES.WAXHOLM + } as any, + } + }) + + expect(obs).toBeObservable( + hot('(a|)', { + a: { + navigation: null + } + }) + ) + }) + }) + }) + }) + }) + + describe('> if selected atlas has no matching tmpl space', () => { + + it('> should emit gernal error', () => { + + }) + }) + + describe('> if selected atlas has matching tmpl', () => { + + describe('> if parc is empty array', () => { + it('> should emit with falsy as payload', () => { + + }) + }) + describe('> if no parc has eligible @id', () => { + + it('> should emit with falsy as payload', () => { + + }) + }) + + describe('> if some parc has eligible @id', () => { + describe('> if no @version is available', () => { + + it('> selects the first parc', () => { + + }) + }) + + describe('> if @version is available', () => { + + describe('> if there exist an entry without @next attribute', () => { + + it('> selects the first one without @next attribute', () => { + }) + }) + describe('> if there exist no entry without @next attribute', () => { + + it('> selects the first one without @next attribute', () => { + + }) + }) + }) + }) + }) + + describe('> onNavigateToRegion', () => { + beforeEach(async () => { + actions$ = hot('a', { + a: actions.navigateToRegion({ + region: hoc1left + }) + }) + const mockStore = TestBed.inject(MockStore) + mockStore.overrideSelector(selectors.selectedAtlas, { + "@id": IDS.ATLAES.HUMAN + } as SapiAtlasModel) + mockStore.overrideSelector(selectors.selectedTemplate, { + "@id": IDS.TEMPLATES.MNI152 + } as SapiSpaceModel) + mockStore.overrideSelector(selectors.selectedParcellation, { + "@id": IDS.PARCELLATION.JBA29 + } as SapiParcellationModel) + }) + + describe('> if atlas, template, parc is not set', () => { + const atp = [{ + name: 'atlas', + atpSelector: selectors.selectedAtlas + },{ + name: 'template', + atpSelector: selectors.selectedTemplate + },{ + name: 'parcellation', + atpSelector: selectors.selectedParcellation + }] + for (const { name, atpSelector } of atp) { + describe(`> if ${name} is unset`, () => { + + beforeEach(() => { + const mockStore = TestBed.inject(MockStore) + mockStore.overrideSelector(atpSelector, null) + }) + + it('> returns general error', () => { + const effect = TestBed.inject(Effect) + expect(effect.onNavigateToRegion).toBeObservable( + hot('a', { + a: mainActions.generalActionError({ + message: `atlas, template, parcellation or region not set` + }) + }) + ) + }) + }) + } + }) + + describe('> if atlas, template, parc is set, but region unset', () => { + + beforeEach(() => { + actions$ = hot('a', { + a: actions.navigateToRegion({ + region: null + }) + }) + }) + it('> returns general error', () => { + const effect = TestBed.inject(Effect) + expect(effect.onNavigateToRegion).toBeObservable( + hot('a', { + a: mainActions.generalActionError({ + message: `atlas, template, parcellation or region not set` + }) + }) + ) + }) + }) + + describe('> if inputs are fine', () => { + let getRegionSpy: jasmine.Spy + let regionGetDetailSpy: jasmine.Spy = jasmine.createSpy() + beforeEach(() => { + const sapi = TestBed.inject(SAPI) + getRegionSpy = spyOn(sapi, 'getRegion') + getRegionSpy.and.returnValue({ + getDetail: regionGetDetailSpy + }) + regionGetDetailSpy.and.returnValue( + of(hoc1leftCentroid) + ) + }) + afterEach(() => { + if (getRegionSpy) getRegionSpy.calls.reset() + if (regionGetDetailSpy) regionGetDetailSpy.calls.reset() + }) + it('> getRegionDetailSpy is called, and calls navigateTo', () => { + const eff = TestBed.inject(Effect) + expect(eff.onNavigateToRegion).toBeObservable( + hot(`a`, { + a: actions.navigateTo({ + navigation: { + position: [1e6, 2e6, 3e6] + }, + animation: true + }) + }) + ) + expect(getRegionSpy).toHaveBeenCalledTimes(1) + expect(getRegionSpy).toHaveBeenCalledWith(IDS.ATLAES.HUMAN, IDS.PARCELLATION.JBA29, hoc1left["@id"]) + }) + + describe('> mal formed return', () => { + describe('> returns null', () => { + beforeEach(() => { + + regionGetDetailSpy.and.returnValue( + of(null) + ) + }) + it('> generalactionerror', () => { + + const eff = TestBed.inject(Effect) + expect(eff.onNavigateToRegion).toBeObservable( + hot(`a`, { + a: mainActions.generalActionError({ + message: `getting region detail error! cannot get coordinates` + }) + }) + ) + }) + }) + describe('> general throw', () => { + beforeEach(() => { + regionGetDetailSpy.and.returnValue( + throwError(`oh noes`) + ) + }) + it('> generalactionerror', () => { + + const eff = TestBed.inject(Effect) + expect(eff.onNavigateToRegion).toBeObservable( + hot(`a`, { + a: mainActions.generalActionError({ + message: `Error getting region centroid` + }) + }) + ) + }) + + }) + describe('> does not contain props attr', () => { + + beforeEach(() => { + regionGetDetailSpy.and.returnValue( + of(hoc1left) + ) + }) + it('> generalactionerror', () => { + + const eff = TestBed.inject(Effect) + expect(eff.onNavigateToRegion).toBeObservable( + hot(`a`, { + a: mainActions.generalActionError({ + message: `getting region detail error! cannot get coordinates` + }) + }) + ) + }) + }) + }) + }) + }) + }) +}) \ No newline at end of file diff --git a/src/state/atlasSelection/effects.ts b/src/state/atlasSelection/effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c95b75602df2ec483983d910f61ceccdcc7525d --- /dev/null +++ b/src/state/atlasSelection/effects.ts @@ -0,0 +1,449 @@ +import { Injectable } from "@angular/core"; +import { Actions, createEffect, ofType } from "@ngrx/effects"; +import { concat, forkJoin, merge, Observable, of } from "rxjs"; +import { catchError, filter, map, mapTo, switchMap, switchMapTo, take, withLatestFrom } from "rxjs/operators"; +import { SAPI, SapiAtlasModel, SapiParcellationModel, SAPIRegion, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; +import * as mainActions from "../actions" +import { select, Store } from "@ngrx/store"; +import { selectors, actions } from '.' +import { fromRootStore } from "./util"; +import { AtlasSelectionState } from "./const" +import { ParcellationIsBaseLayer } from "src/atlasComponents/sapiViews/core/parcellation/parcellationIsBaseLayer.pipe"; +import { OrderParcellationByVersionPipe } from "src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe"; +import { atlasAppearance, atlasSelection } from ".."; +import { ParcellationSupportedInSpacePipe } from "src/atlasComponents/sapiViews/util/parcellationSupportedInSpace.pipe"; +import { InterSpaceCoordXformSvc } from "src/atlasComponents/sapi/core/space/interSpaceCoordXform.service"; + +type OnTmplParcHookArg = { + previous: { + atlas: SapiAtlasModel + template: SapiSpaceModel + parcellation: SapiParcellationModel + } + current: { + atlas: SapiAtlasModel + template: SapiSpaceModel + parcellation: SapiParcellationModel + } +} + +@Injectable() +export class Effect { + + onTemplateParcSelectionPostHook: ((arg: OnTmplParcHookArg) => Observable<Partial<AtlasSelectionState>>)[] = [ + /** + * This hook gets the region associated with the selected parcellation and template, + * and then set selectedParcellationAllRegions to it + */ + ({ current }) => { + const { atlas, parcellation, template } = current + return ( + !!atlas && !!parcellation && !!template + ? this.sapiSvc.getParcRegions(atlas["@id"], parcellation["@id"], template["@id"]) + : of([]) + ).pipe( + map(regions => { + return { + selectedParcellationAllRegions: regions + } + }) + ) + }, + ({ current, previous }) => { + const prevSpcName = InterSpaceCoordXformSvc.TmplIdToValidSpaceName(previous?.template?.["@id"]) + const currSpcName = InterSpaceCoordXformSvc.TmplIdToValidSpaceName(current?.template?.["@id"]) + + /** + * if trans-species, return default state for navigation + */ + if (previous?.atlas?.["@id"] !== current?.atlas?.["@id"]) { + return of({ + navigation: null + }) + } + + /** + * if either space name is undefined, return default state for navigation + */ + if (!prevSpcName || !currSpcName) { + return of({ + navigation: atlasSelection.defaultState.navigation + }) + } + return this.store.pipe( + select(atlasSelection.selectors.navigation), + take(1), + switchMap(({ position, ...rest }) => + this.interSpaceCoordXformSvc.transform(prevSpcName, currSpcName, position as [number, number, number]).pipe( + map(value => { + if (value.status === "error") { + return {} + } + return { + navigation: { + ...rest, + position: value.result, + } + } as Partial<AtlasSelectionState> + }) + ) + ) + ) + } + ] + + parcSupportedInSpacePipe = new ParcellationSupportedInSpacePipe(this.sapiSvc) + onTemplateParcSelection = createEffect(() => merge<{ template: SapiSpaceModel, parcellation: SapiParcellationModel }>( + this.action.pipe( + ofType(actions.selectTemplate), + map(({ template }) => { + return { + template, + parcellation: null + } + }) + ), + this.action.pipe( + ofType(actions.selectParcellation), + map(({ parcellation }) => { + return { + template: null, + parcellation + } + }) + ) + ).pipe( + withLatestFrom(this.store), + switchMap(([ { template, parcellation }, store ]) => { + const currTmpl = selectors.selectedTemplate(store) + const currParc = selectors.selectedParcellation(store) + const currAtlas = selectors.selectedAtlas(store) + return this.parcSupportedInSpacePipe.transform( + parcellation || currParc, + template || currTmpl + ).pipe( + switchMap(flag => { + /** + * if desired parc is supported in tmpl, emit them + */ + if (flag) { + return of({ + atlas: currAtlas, + template: template || currTmpl, + parcellation: parcellation || currParc, + }) + } + /** + * if template is defined, find the first parcellation that is supported + */ + if (!!template) { + return concat( + ...currAtlas.parcellations.map( + p => this.parcSupportedInSpacePipe.transform(p["@id"], template).pipe( + filter(flag => flag), + switchMap(() => this.sapiSvc.getParcDetail(currAtlas["@id"], p['@id'])), + ) + ) + ).pipe( + take(1), + map(parcellation => { + return { + atlas: currAtlas, + template, + parcellation + } + }) + ) + } + if (!!parcellation) { + return concat( + ...currAtlas.spaces.map( + sp => this.parcSupportedInSpacePipe.transform(parcellation["@id"], sp["@id"]).pipe( + filter(flag => flag), + switchMap(() => this.sapiSvc.getSpaceDetail(currAtlas["@id"], sp['@id'])), + ) + ) + ).pipe( + take(1), + map(template => { + return { + atlas: currAtlas, + template, + parcellation + } + }) + ) + } + throw new Error(`neither template nor parcellation has been defined!`) + }), + switchMap(({ atlas, template, parcellation }) => + forkJoin( + this.onTemplateParcSelectionPostHook.map(fn => fn({ previous: { atlas: currAtlas, template: currTmpl, parcellation: currParc }, current: { atlas, template, parcellation } })) + ).pipe( + map(partialStates => { + let returnState: Partial<AtlasSelectionState> = { + selectedAtlas: atlas, + selectedTemplate: template, + selectedParcellation: parcellation + } + for (const s of partialStates) { + returnState = { + ...returnState, + ...s, + } + } + return actions.setAtlasSelectionState(returnState) + }) + ) + ) + ) + }) + )) + + onAtlasSelectionSelectTmplParc = createEffect(() => this.action.pipe( + ofType(actions.selectAtlas), + filter(action => !!action.atlas), + switchMap(({ atlas }) => { + const selectedParc = atlas.parcellations.find(p => /290/.test(p["@id"])) || atlas.parcellations[0] + return this.sapiSvc.getParcDetail(atlas["@id"], selectedParc["@id"], { priority: 10 }).pipe( + map(parcellation => { + return { + parcellation, + atlas + } + }) + ) + }), + switchMap(({ atlas, parcellation }) => { + const spacdIds = parcellation.brainAtlasVersions.map(bas => bas.coordinateSpace) as { "@id": string }[] + return forkJoin( + spacdIds.filter( + spaceId => atlas.spaces.map(spc => spc["@id"]).indexOf(spaceId["@id"]) >= 0 + ).map(spaceId => + this.sapiSvc.getSpaceDetail(atlas["@id"], spaceId["@id"]) + ) + ).pipe( + switchMap(spaces => { + const selectedSpace = spaces.find(s => /152/.test(s.fullName)) || spaces[0] + return forkJoin( + this.onTemplateParcSelectionPostHook.map(fn => fn({ previous: null, current: { atlas, parcellation, template: selectedSpace } })) + ).pipe( + map(partialStates => { + + let returnState: Partial<AtlasSelectionState> = { + selectedAtlas: atlas, + selectedTemplate: selectedSpace, + selectedParcellation: parcellation + } + for (const s of partialStates) { + returnState = { + ...returnState, + ...s, + } + } + return actions.setAtlasSelectionState(returnState) + }) + ) + }) + ) + }) + )) + + onATPSelectionClearBaseLayerColorMap = createEffect(() => this.store.pipe( + select(selectors.selectedParcAllRegions), + withLatestFrom( + this.store.pipe( + select(atlasAppearance.selectors.customLayers), + map(layers => layers.filter(l => l.clType === "baselayer/colormap")) + ) + ), + switchMap(([regions, layers]) => { + const map = new Map<SapiRegionModel, number[]>() + for (const region of regions) { + map.set(region, SAPIRegion.GetDisplayColor(region)) + } + const actions = [ + ...layers.map(({ id }) => + atlasAppearance.actions.removeCustomLayer({ + id + }) + ), + atlasAppearance.actions.addCustomLayer({ + customLayer: { + clType: "baselayer/colormap", + id: 'base-colormap-id', + colormap: map + } + }) + ] + return of(...actions) + }) + )) + + + onAtlasSelClearStandAloneVolumes = createEffect(() => this.action.pipe( + ofType(actions.selectAtlas), + mapTo(actions.setStandAloneVolumes({ + standAloneVolumes: [] + })) + )) + + onClearRegion = createEffect(() => this.action.pipe( + ofType(actions.clearSelectedRegions), + mapTo(actions.setSelectedRegions({ + regions: [] + })) + )) + + onNonBaseLayerRemoval = createEffect(() => this.action.pipe( + ofType(actions.clearNonBaseParcLayer), + switchMapTo( + this.store.pipe( + fromRootStore.allAvailParcs(this.sapiSvc), + map(parcs => { + const baseLayers = parcs.filter(this.parcellationIsBaseLayerPipe.transform) + const newestLayer = this.orderParcellationByVersionPipe.transform(baseLayers) + return actions.selectParcellation({ + parcellation: newestLayer + }) + }) + ) + ) + )) + + private parcellationIsBaseLayerPipe = new ParcellationIsBaseLayer() + private orderParcellationByVersionPipe = new OrderParcellationByVersionPipe() + + onClearStandAloneVolumes = createEffect(() => this.action.pipe( + ofType(actions.clearStandAloneVolumes), + mapTo(actions.setStandAloneVolumes({ + standAloneVolumes: [] + })) + )) + + /** + * nb for template selection + * navigation should be transformed + * see selectTemplate$ in spec.ts + */ + onSelectATPById = createEffect(() => this.action.pipe( + ofType(actions.selectATPById), + mapTo(mainActions.generalActionError({ + message: `NYI, onSelectATPById` + })) + )) + + onClearViewerMode = createEffect(() => this.action.pipe( + ofType(actions.clearViewerMode), + mapTo(actions.setViewerMode({ viewerMode: null })) + )) + + onToggleRegionSelectById = createEffect(() => this.action.pipe( + ofType(actions.toggleRegionSelectById), + mapTo(mainActions.generalActionError({ + message: `NYI onToggleRegionSelectById` + })) + )) + + onNavigateToRegion = createEffect(() => this.action.pipe( + ofType(actions.navigateToRegion), + withLatestFrom( + this.store.pipe( + select(selectors.selectedTemplate) + ), + this.store.pipe( + select(selectors.selectedAtlas) + ), + this.store.pipe( + select(selectors.selectedParcellation) + ) + ), + switchMap(([{ region }, selectedTemplate, selectedAtlas, selectedParcellation]) => { + if (!selectedAtlas || !selectedTemplate || !selectedParcellation || !region) { + return of( + mainActions.generalActionError({ + message: `atlas, template, parcellation or region not set` + }) + ) + } + + if (region.hasAnnotation?.bestViewPoint && region.hasAnnotation.bestViewPoint.coordinateSpace['@id'] === selectedTemplate["@id"]) { + return of( + actions.navigateTo({ + animation: true, + navigation: { + position: region.hasAnnotation.bestViewPoint.coordinates.map(v => v.value * 1e6) + } + }) + ) + } + + return this.sapiSvc.getRegion(selectedAtlas['@id'], selectedParcellation['@id'], region["@id"]).getDetail(selectedTemplate["@id"]).pipe( + map(detailedRegion => { + if (!detailedRegion?.hasAnnotation?.bestViewPoint?.coordinates) { + return mainActions.generalActionError({ + message: `getting region detail error! cannot get coordinates` + }) + } + return actions.navigateTo({ + animation: true, + navigation: { + position: detailedRegion.hasAnnotation.bestViewPoint.coordinates.map(v => v.value * 1e6) + } + }) + }), + catchError((_err, _obs) => of( + mainActions.generalActionError({ + message: `Error getting region centroid` + }) + )) + ) + }) + )) + + onSelAtlasTmplParcClearRegion = createEffect(() => merge( + this.action.pipe( + ofType(actions.selectAtlas) + ), + this.action.pipe( + ofType(actions.selectTemplate) + ), + this.action.pipe( + ofType(actions.selectParcellation) + ) + ).pipe( + switchMapTo( + of( + actions.setSelectedRegions({ + regions: [] + }) + ) + ) + )) + + onRegionToggleSelect = createEffect(() => this.action.pipe( + ofType(actions.toggleRegionSelect), + withLatestFrom( + this.store.pipe( + select(selectors.selectedRegions) + ) + ), + map(([ { region }, regions ]) => { + const selectedRegionsIndicies = regions.map(r => r["@id"]) + const roiIndex = selectedRegionsIndicies.indexOf(region["@id"]) + return actions.setSelectedRegions({ + regions: roiIndex >= 0 + ? [...regions.slice(0, roiIndex), ...regions.slice(roiIndex + 1)] + : [...regions, region] + }) + }) + )) + + constructor( + private action: Actions, + private sapiSvc: SAPI, + private store: Store, + private interSpaceCoordXformSvc: InterSpaceCoordXformSvc, + ){ + } +} \ No newline at end of file diff --git a/src/state/atlasSelection/index.ts b/src/state/atlasSelection/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ef795456578636c52a28ce08189b4feec31bdb5 --- /dev/null +++ b/src/state/atlasSelection/index.ts @@ -0,0 +1,6 @@ +export * as selectors from "./selectors" +export { fromRootStore } from "./util" +export { nameSpace, AtlasSelectionState } from "./const" +export { reducer, defaultState } from "./store" +export * as actions from "./actions" +export { Effect } from "./effects" \ No newline at end of file diff --git a/src/state/atlasSelection/selectors.ts b/src/state/atlasSelection/selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..ad3079a560f2d12eb3cf5655279122b8cf21cd75 --- /dev/null +++ b/src/state/atlasSelection/selectors.ts @@ -0,0 +1,51 @@ +import { createSelector } from "@ngrx/store" +import { nameSpace, AtlasSelectionState } from "./const" + +export const viewerStateHelperStoreName = 'viewerStateHelper' + +export const selectStore = (state: any) => state[nameSpace] as AtlasSelectionState + +export const selectedAtlas = createSelector( + selectStore, + state => state.selectedAtlas +) + +export const selectedTemplate = createSelector( + selectStore, + state => state.selectedTemplate +) + +export const selectedParcellation = createSelector( + selectStore, + state => state.selectedParcellation +) + +export const selectedParcAllRegions = createSelector( + selectStore, + state => state.selectedParcellationAllRegions +) + +export const selectedRegions = createSelector( + selectStore, + state => state.selectedRegions +) + +export const standaloneVolumes = createSelector( + selectStore, + state => state.standAloneVolumes +) + +export const navigation = createSelector( + selectStore, + state => state.navigation +) + +export const viewerMode = createSelector( + selectStore, + state => state.viewerMode +) + +export const breadCrumbs = createSelector( + selectStore, + state => state.breadcrumbs +) \ No newline at end of file diff --git a/src/state/atlasSelection/store.ts b/src/state/atlasSelection/store.ts new file mode 100644 index 0000000000000000000000000000000000000000..08848c1efa9b81e649bfc8d7c29d3cc6581f77ed --- /dev/null +++ b/src/state/atlasSelection/store.ts @@ -0,0 +1,139 @@ +import { createReducer, on } from "@ngrx/store"; +import { SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; +import * as actions from "./actions" +import { AtlasSelectionState } from "./const" + +function getRegionLabelIndex(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, parc: SapiParcellationModel, region: SapiRegionModel) { + const lblIdx = Number(region?.hasAnnotation?.internalIdentifier) + if (isNaN(lblIdx)) return null + return lblIdx +} + +export const defaultState: AtlasSelectionState = { + selectedAtlas: null, + selectedParcellation: null, + selectedParcellationAllRegions: [], + selectedRegions: [], + selectedTemplate: null, + standAloneVolumes: [], + navigation: null, + viewerMode: null, + breadcrumbs: [] +} + +const reducer = createReducer( + defaultState, + on( + actions.setAtlasSelectionState, + (state, partialState) => { + return { + ...state, + ...partialState + } + } + ), + on( + actions.setSelectedParcellationAllRegions, + (state, { regions }) => { + return { + ...state, + selectedParcellationAllRegions: regions + } + } + ), + on( + actions.selectRegion, + (state, { region }) => { + /** + * if roi does not have visualizedIn defined + * or internal identifier + * + * ignore + */ + const { selectedAtlas, selectedParcellation, selectedTemplate } = state + if ( + !region.hasAnnotation?.visualizedIn + && !getRegionLabelIndex(selectedAtlas, selectedTemplate, selectedParcellation, region) + ) { + return { ...state } + } + const selected = state.selectedRegions.includes(region) + return { + ...state, + selectedRegions: selected + ? [ ] + : [ region ] + } + } + ), + on( + actions.setSelectedRegions, + (state, { regions }) => { + return { + ...state, + selectedRegions: regions + } + } + ), + on( + actions.setStandAloneVolumes, + (state, { standAloneVolumes }) => { + return { + ...state, + standAloneVolumes + } + } + ), + on( + actions.setNavigation, + (state, { navigation }) => { + return { + ...state, + navigation + } + } + ), + on( + actions.setViewerMode, + (state, { viewerMode }) => { + return { + ...state, + viewerMode + } + } + ), + on( + actions.showBreadCrumb, + (state, { breadcrumb }) => { + return { + ...state, + breadcrumbs: [ + ...state.breadcrumbs.filter(bc => bc.id !== breadcrumb.id), + breadcrumb + ] + } + } + ), + on( + actions.selectAtlas, + (state, { atlas }) => { + return { + ...state, + selectedAtlas: atlas + } + } + ), + on( + actions.dismissBreadCrumb, + (state, { id }) => { + return { + ...state, + breadcrumbs: state.breadcrumbs.filter(bc => bc.id !== id) + } + } + ) +) + +export { + reducer +} diff --git a/src/state/atlasSelection/util.ts b/src/state/atlasSelection/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..cf64466bb4375a6ffa06bb50ec99fea60ee5b8fd --- /dev/null +++ b/src/state/atlasSelection/util.ts @@ -0,0 +1,53 @@ +import { createSelector, select } from "@ngrx/store"; +import { forkJoin, pipe } from "rxjs"; +import { distinctUntilChanged, map, switchMap } from "rxjs/operators"; +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiSpaceModel } from "src/atlasComponents/sapi"; +import { jsonEqual } from "src/util/json"; +import * as selectors from "./selectors" + +const allAvailSpaces = (sapi: SAPI) => pipe( + select(selectors.selectedAtlas), + switchMap(atlas => forkJoin( + atlas.spaces.map(spcWId => sapi.getSpaceDetail(atlas["@id"], spcWId["@id"]))) + ) +) + +const allAvailParcs = (sapi: SAPI) => pipe( + select(selectors.selectedAtlas), + switchMap(atlas => + forkJoin( + atlas.parcellations.map(parcWId => sapi.getParcDetail(atlas["@id"], parcWId["@id"])) + ) + ) +) +const allAvailSpacesParcs = (sapi: SAPI) => pipe( + select(selectors.selectedAtlas), + switchMap(atlas => + forkJoin({ + spaces: atlas.spaces.map(spcWId => sapi.getSpaceDetail(atlas["@id"], spcWId["@id"])), + parcellation: atlas.parcellations.map(parcWId => sapi.getParcDetail(atlas["@id"], parcWId["@id"])), + }) + ) +) + +const nonDistinctATP = createSelector( + selectors.selectedAtlas, + selectors.selectedTemplate, + selectors.selectedParcellation, + (atlas, template, parcellation) => ({ atlas, template, parcellation }) +) + +const distinctATP = () => pipe( + select(nonDistinctATP), + distinctUntilChanged( + jsonEqual((o, n) => o?.["@id"] === n?.["@id"]) + ), + map(val => val as { atlas: SapiAtlasModel, parcellation: SapiParcellationModel, template: SapiSpaceModel }) +) + +export const fromRootStore = { + allAvailSpaces, + allAvailParcs, + allAvailSpacesParcs, + distinctATP, +} diff --git a/src/state/const.ts b/src/state/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..60e44f40724c7a562efb1040af23b1f001adacf9 --- /dev/null +++ b/src/state/const.ts @@ -0,0 +1,13 @@ +import { annotation, atlasAppearance, atlasSelection, plugins, userInteraction, userInterface, userPreference } from "." + +export const nameSpace = `[state]` + +export type MainState = { + [userPreference.nameSpace]: userPreference.UserPreference + [atlasSelection.nameSpace]: atlasSelection.AtlasSelectionState + [userInterface.nameSpace]: userInterface.UiStore + [userInteraction.nameSpace]: userInteraction.UserInteraction + [annotation.nameSpace]: annotation.AnnotationState + [plugins.nameSpace]: plugins.PluginStore + [atlasAppearance.nameSpace]: atlasAppearance.AtlasAppearanceStore +} diff --git a/src/state/effects/viewerState.useEffect.spec.ts b/src/state/effects/viewerState.useEffect.spec.ts deleted file mode 100644 index 37c7b0ee9f18297b0268a6459b74863e6f07d2b9..0000000000000000000000000000000000000000 --- a/src/state/effects/viewerState.useEffect.spec.ts +++ /dev/null @@ -1,738 +0,0 @@ -import { cvtNehubaConfigToNavigationObj, ViewerStateControllerUseEffect, defaultNavigationObject, defaultNehubaConfigObject } from './viewerState.useEffect' -import { Observable, of, throwError } from 'rxjs' -import { TestBed } from '@angular/core/testing' -import { provideMockActions } from '@ngrx/effects/testing' -import { MockStore, provideMockStore } from '@ngrx/store/testing' -import { defaultRootState, generalActionError } from 'src/services/stateStore.service' -import { Injectable } from '@angular/core' -import { TemplateCoordinatesTransformation, ITemplateCoordXformResp } from 'src/services/templateCoordinatesTransformation.service' -import { hot } from 'jasmine-marbles' -import { AngularMaterialModule } from 'src/sharedModules' -import { HttpClientModule } from '@angular/common/http' -import { viewerStateFetchedTemplatesSelector, viewerStateNavigateToRegion, viewerStateNavigationStateSelector, viewerStateNewViewer, viewerStateSelectAtlas, viewerStateSelectTemplateWithName } from 'src/services/state/viewerState.store.helper' -import { viewerStateFetchedAtlasesSelector, viewerStateGetSelectedAtlas, viewerStateSelectedParcellationSelector, viewerStateSelectedTemplateSelector } from 'src/services/state/viewerState/selectors' -import { CONST } from 'common/constants' -import { PureContantService } from 'src/util' -import { viewerStateChangeNavigation } from 'src/services/state/viewerState/actions' - -let returnPosition = null -const dummyParc1 = { - name: 'dummyParc1' -} -const dummyTmpl1 = { - '@id': 'dummyTmpl1-id', - name: 'dummyTmpl1', - parcellations: [dummyParc1], - nehubaConfig: { - dataset: { - initialNgState: { - ...defaultNehubaConfigObject - } - } - } -} - -const dummyParc2 = { - name: 'dummyParc2' -} -const dummyTmpl2 = { - '@id': 'dummyTmpl2-id', - name: 'dummyTmpl2', - parcellations: [dummyParc2], - nehubaConfig: { - dataset: { - initialNgState: { - ...defaultNehubaConfigObject - } - } - } -} - -@Injectable() -class MockCoordXformService{ - getPointCoordinatesForTemplate(src:string, tgt: string, pos: [number, number, number]): Observable<ITemplateCoordXformResp>{ - return returnPosition - ? of({ status: 'completed', result: returnPosition } as ITemplateCoordXformResp) - : of({ status: 'error', statusText: 'Failing query' } as ITemplateCoordXformResp) - } -} - -const initialState = JSON.parse(JSON.stringify( defaultRootState )) -const mockFetchedTemplates = [ - dummyTmpl2, - dummyTmpl1 -] -initialState.viewerState.fetchedTemplates = mockFetchedTemplates -initialState.viewerState.templateSelected = dummyTmpl2 -const currentNavigation = { - position: [4, 5, 6], - orientation: [0, 0, 0, 1], - perspectiveOrientation: [ 0, 0, 0, 1], - perspectiveZoom: 2e5, - zoom: 1e5 -} -initialState.viewerState.navigation = currentNavigation - -class MockPureConstantService{ - allFetchingReady$ = of(true) - initFetchTemplate$ = of([]) - - getRegionDetail(){ - return of(null) - } -} - -const mockPureConstantService = new MockPureConstantService() -describe('> viewerState.useEffect.ts', () => { - describe('> ViewerStateControllerUseEffect', () => { - let actions$: Observable<any> - let spy: jasmine.Spy - let mockStore: MockStore - beforeEach(() => { - - const mock = new MockCoordXformService() - spy = spyOn(mock, 'getPointCoordinatesForTemplate').and.callThrough() - returnPosition = null - - TestBed.configureTestingModule({ - imports: [ - AngularMaterialModule, - HttpClientModule, - ], - providers: [ - ViewerStateControllerUseEffect, - provideMockActions(() => actions$), - provideMockStore({ initialState }), - { - provide: TemplateCoordinatesTransformation, - useValue: mock - }, - { - provide: PureContantService, - useValue: mockPureConstantService - } - ] - }) - - mockStore = TestBed.inject(MockStore) - }) - - describe('> selectTemplate$', () => { - beforeEach(() => { - mockStore.overrideSelector(viewerStateFetchedTemplatesSelector, mockFetchedTemplates) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, dummyParc1) - actions$ = hot( - 'a', - { - a: viewerStateSelectTemplateWithName({ - payload: { - name: dummyTmpl1.name - } - }) - } - ) - }) - describe('> when transiting from template A to template B', () => { - describe('> if the current navigation is correctly formed', () => { - it('> uses current navigation param', () => { - - const viewerStateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect) - expect( - viewerStateCtrlEffect.selectTemplate$ - ).toBeObservable( - hot( - 'a', - { - a: viewerStateNewViewer({ - selectTemplate: dummyTmpl1, - selectParcellation: dummyTmpl1.parcellations[0], - }) - } - ) - ) - expect(spy).toHaveBeenCalledWith( - dummyTmpl2.name, - dummyTmpl1.name, - initialState.viewerState.navigation.position - ) - }) - }) - - describe('> if current navigation is malformed', () => { - it('> if current navigation is undefined, use nehubaConfig of last template', () => { - - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateNavigationStateSelector, null) - const viewerStateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect) - - expect( - viewerStateCtrlEffect.selectTemplate$ - ).toBeObservable( - hot( - 'a', - { - a: viewerStateNewViewer({ - selectTemplate: dummyTmpl1, - selectParcellation: dummyTmpl1.parcellations[0], - }) - } - ) - ) - const { position } = cvtNehubaConfigToNavigationObj(dummyTmpl2.nehubaConfig.dataset.initialNgState) - - expect(spy).toHaveBeenCalledWith( - dummyTmpl2.name, - dummyTmpl1.name, - position - ) - }) - - it('> if current navigation is empty object, use nehubaConfig of last template', () => { - - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateNavigationStateSelector, {}) - const viewerStateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect) - - expect( - viewerStateCtrlEffect.selectTemplate$ - ).toBeObservable( - hot( - 'a', - { - a: viewerStateNewViewer({ - selectTemplate: dummyTmpl1, - selectParcellation: dummyTmpl1.parcellations[0], - }) - } - ) - ) - const { position } = cvtNehubaConfigToNavigationObj(dummyTmpl2.nehubaConfig.dataset.initialNgState) - - expect(spy).toHaveBeenCalledWith( - dummyTmpl2.name, - dummyTmpl1.name, - position - ) - }) - }) - - }) - - it('> if coordXform returns error', () => { - const viewerStateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect) - expect( - viewerStateCtrlEffect.selectTemplate$ - ).toBeObservable( - hot( - 'a', - { - a: viewerStateNewViewer({ - selectTemplate: dummyTmpl1, - selectParcellation: dummyTmpl1.parcellations[0], - }) - } - ) - ) - }) - - it('> if coordXform complete', () => { - returnPosition = [ 1.11e6, 2.22e6, 3.33e6 ] - - const viewerStateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect) - const updatedColin = JSON.parse( JSON.stringify( dummyTmpl1 ) ) - const initialNgState = updatedColin.nehubaConfig.dataset.initialNgState - const updatedColinNavigation = updatedColin.nehubaConfig.dataset.initialNgState.navigation - - const { zoom, orientation, perspectiveOrientation, position, perspectiveZoom } = currentNavigation - - for (const idx of [0, 1, 2]) { - updatedColinNavigation.pose.position.voxelCoordinates[idx] = returnPosition[idx] / updatedColinNavigation.pose.position.voxelSize[idx] - } - updatedColinNavigation.zoomFactor = zoom - updatedColinNavigation.pose.orientation = orientation - initialNgState.perspectiveOrientation = perspectiveOrientation - initialNgState.perspectiveZoom = perspectiveZoom - - expect( - viewerStateCtrlEffect.selectTemplate$ - ).toBeObservable( - hot( - 'a', - { - a: viewerStateNewViewer({ - selectTemplate: updatedColin, - selectParcellation: updatedColin.parcellations[0], - }) - } - ) - ) - }) - - }) - - describe('> navigateToRegion$', () => { - const setAction = region => { - actions$ = hot( - 'a', - { - a: viewerStateNavigateToRegion({ - payload: { region } - }) - } - ) - } - let mockStore: MockStore - beforeEach(() => { - - mockStore = TestBed.inject(MockStore) - - mockStore.overrideSelector(viewerStateGetSelectedAtlas, { '@id': 'foo-bar-atlas'}) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, { '@id': 'foo-bar-template'}) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, { '@id': 'foo-bar-parcellation'}) - }) - describe('> if atlas, template, parc is not set', () => { - beforeEach(() => { - const region = { - name: 'foo bar' - } - setAction(region) - }) - describe('> if atlas is unset', () => { - beforeEach(() => { - mockStore.overrideSelector(viewerStateGetSelectedAtlas, null) - }) - it('> returns general error', () => { - const effect = TestBed.inject(ViewerStateControllerUseEffect) - expect(effect.navigateToRegion$).toBeObservable( - hot('a', { - a: generalActionError({ - message: 'Go to region: region / atlas / template / parcellation not defined.' - }) - }) - ) - }) - }) - describe('> if template is unset', () => { - beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, null) - }) - it('> returns general error', () => { - const effect = TestBed.inject(ViewerStateControllerUseEffect) - expect(effect.navigateToRegion$).toBeObservable( - hot('a', { - a: generalActionError({ - message: 'Go to region: region / atlas / template / parcellation not defined.' - }) - }) - ) - }) - }) - describe('> if parc is unset', () => { - beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, null) - }) - it('> returns general error', () => { - const effect = TestBed.inject(ViewerStateControllerUseEffect) - expect(effect.navigateToRegion$).toBeObservable( - hot('a', { - a: generalActionError({ - message: 'Go to region: region / atlas / template / parcellation not defined.' - }) - }) - ) - }) - }) - }) - describe('> if atlas, template, parc is set, but region unset', () => { - beforeEach(() => { - setAction(null) - }) - it('> returns general error', () => { - const effect = TestBed.inject(ViewerStateControllerUseEffect) - expect(effect.navigateToRegion$).toBeObservable( - hot('a', { - a: generalActionError({ - message: 'Go to region: region / atlas / template / parcellation not defined.' - }) - }) - ) - }) - }) - - describe('> if inputs are fine', () => { - let getRegionDetailSpy: jasmine.Spy - const region = { - name: 'foo bar' - } - beforeEach(() => { - getRegionDetailSpy = spyOn(mockPureConstantService, 'getRegionDetail').and.callThrough() - setAction(region) - }) - afterEach(() => { - getRegionDetailSpy.calls.reset() - }) - - it('> getRegionDetailSpy is called', () => { - const ctrl = TestBed.inject(ViewerStateControllerUseEffect) - - // necessary to trigger the emit - expect( - ctrl.navigateToRegion$ - ).toBeObservable( - hot('a', { - a: generalActionError({ - message: 'Fetching region detail error: Error: region does not have props defined!' - }) - }) - ) - - expect(getRegionDetailSpy).toHaveBeenCalled() - }) - - describe('> mal formed return', () => { - describe('> returns null', () => { - it('> generalactionerror', () => { - const ctrl = TestBed.inject(ViewerStateControllerUseEffect) - expect( - ctrl.navigateToRegion$ - ).toBeObservable( - hot('a', { - a: generalActionError({ - message: 'Fetching region detail error: Error: region does not have props defined!' - }) - }) - ) - }) - }) - describe('> general throw', () => { - const msg = 'oh no!' - beforeEach(() => { - getRegionDetailSpy.and.callFake(() => throwError(msg)) - }) - - it('> generalactionerror', () => { - const ctrl = TestBed.inject(ViewerStateControllerUseEffect) - expect( - ctrl.navigateToRegion$ - ).toBeObservable( - hot('a', { - a: generalActionError({ - message: `Fetching region detail error: ${msg}` - }) - }) - ) - }) - - }) - describe('> does not contain props attr', () => { - - beforeEach(() => { - getRegionDetailSpy.and.callFake(() => of({ - name: 'foo-bar' - })) - }) - - it('> generalactionerror', () => { - const ctrl = TestBed.inject(ViewerStateControllerUseEffect) - expect( - ctrl.navigateToRegion$ - ).toBeObservable( - hot('a', { - a: generalActionError({ - message: `Fetching region detail error: Error: region does not have props defined!` - }) - }) - ) - }) - }) - - describe('> does not contain props.length === 0', () => { - - beforeEach(() => { - getRegionDetailSpy.and.callFake(() => of({ - name: 'foo-bar', - props: {} - })) - }) - - it('> generalactionerror', () => { - const ctrl = TestBed.inject(ViewerStateControllerUseEffect) - expect( - ctrl.navigateToRegion$ - ).toBeObservable( - hot('a', { - a: generalActionError({ - message: `Fetching region detail error: Error: region does not have props defined!` - }) - }) - ) - }) - }) - }) - - describe('> wellformed response', () => { - beforeEach(() => { - - beforeEach(() => { - getRegionDetailSpy.and.callFake(() => of({ - name: 'foo-bar', - props: { - components: { - centroid: [1,2,3] - } - } - })) - }) - - it('> emits viewerStateChangeNavigation', () => { - const ctrl = TestBed.inject(ViewerStateControllerUseEffect) - expect( - ctrl.navigateToRegion$ - ).toBeObservable( - hot('a', { - a: viewerStateChangeNavigation({ - navigation: { - position: [1e6,2e6,3e6], - animation: {} - } - }) - }) - ) - }) - }) - }) - }) - }) - - describe('> onSelectAtlasSelectTmplParc$', () => { - let mockStore: MockStore - beforeEach(() => { - mockStore = TestBed.inject(MockStore) - }) - - it('> if atlas not found, return general error', () => { - mockStore.overrideSelector(viewerStateFetchedTemplatesSelector, []) - mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, []) - actions$ = hot('a', { - a: viewerStateSelectAtlas({ - atlas: { - ['@id']: 'foo-bar', - } - }) - }) - - const viewerSTateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect) - expect( - viewerSTateCtrlEffect.onSelectAtlasSelectTmplParc$ - ).toBeObservable( - hot('a', { - a: generalActionError({ - message: CONST.ATLAS_NOT_FOUND - }) - }) - ) - }) - - describe('> if atlas found', () => { - const mockParc1 = { - ['@id']: 'parc-1', - availableIn: [{ - ['@id']: 'test-1' - }] - } - const mockParc0 = { - ['@id']: 'parc-0', - availableIn: [{ - ['@id']: 'hello world' - }] - } - const mockTmplSpc = { - ['@id']: 'hello world', - availableIn: [ mockParc0 ] - } - const mockTmplSpc1 = { - ['@id']: 'test-1', - availableIn: [ mockParc1 ] - } - - describe('> if template key val is not provided', () => { - describe('> will try to find the id of the first tmpl', () => { - - it('> if fails, will return general error', () => { - - mockStore.overrideSelector(viewerStateFetchedTemplatesSelector, [ - mockTmplSpc1 - ]) - mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, [{ - ['@id']: 'foo-bar', - templateSpaces: [ mockTmplSpc ], - parcellations: [ mockParc0 ] - }]) - actions$ = hot('a', { - a: viewerStateSelectAtlas({ - atlas: { - ['@id']: 'foo-bar', - } - }) - }) - - const viewerSTateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect) - expect( - viewerSTateCtrlEffect.onSelectAtlasSelectTmplParc$ - ).toBeObservable( - hot('a', { - a: generalActionError({ - message: CONST.TEMPLATE_NOT_FOUND - }) - }) - ) - }) - - it('> if succeeds, will dispatch new viewer', () => { - const completeMocktmpl = { - ...mockTmplSpc1, - parcellations: [ mockParc1 ] - } - mockStore.overrideSelector(viewerStateFetchedTemplatesSelector, [ - completeMocktmpl - ]) - mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, [{ - ['@id']: 'foo-bar', - templateSpaces: [ mockTmplSpc1 ], - parcellations: [ mockParc1 ] - }]) - actions$ = hot('a', { - a: viewerStateSelectAtlas({ - atlas: { - ['@id']: 'foo-bar', - } - }) - }) - - const viewerSTateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect) - expect( - viewerSTateCtrlEffect.onSelectAtlasSelectTmplParc$ - ).toBeObservable( - hot('a', { - a: viewerStateNewViewer({ - selectTemplate: completeMocktmpl, - selectParcellation: mockParc1 - }) - }) - ) - }) - - }) - }) - - describe('> if template key val is provided', () => { - - const completeMockTmpl = { - ...mockTmplSpc, - parcellations: [ mockParc0 ] - } - const completeMocktmpl1 = { - ...mockTmplSpc1, - parcellations: [ mockParc1 ] - } - beforeEach(() => { - - mockStore.overrideSelector(viewerStateFetchedTemplatesSelector, [ - completeMockTmpl, - completeMocktmpl1, - ]) - mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, [{ - ['@id']: 'foo-bar', - templateSpaces: [ mockTmplSpc, mockTmplSpc1 ], - parcellations: [ mockParc0, mockParc1 ] - }]) - }) - it('> will select template.@id', () => { - - actions$ = hot('a', { - a: viewerStateSelectAtlas({ - atlas: { - ['@id']: 'foo-bar', - template: { - ['@id']: mockTmplSpc1['@id'] - } - } - }) - }) - - const viewerSTateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect) - expect( - viewerSTateCtrlEffect.onSelectAtlasSelectTmplParc$ - ).toBeObservable( - hot('a', { - a: viewerStateNewViewer({ - selectTemplate: completeMocktmpl1, - selectParcellation: mockParc1 - }) - }) - ) - - }) - - it('> if template.@id is not defined, will fallback to first template', () => { - - actions$ = hot('a', { - a: viewerStateSelectAtlas({ - atlas: { - ['@id']: 'foo-bar', - template: { - - } as any - } - }) - }) - - const viewerSTateCtrlEffect = TestBed.inject(ViewerStateControllerUseEffect) - expect( - viewerSTateCtrlEffect.onSelectAtlasSelectTmplParc$ - ).toBeObservable( - hot('a', { - a: viewerStateNewViewer({ - selectTemplate: completeMockTmpl, - selectParcellation: mockParc0 - }) - }) - ) - - }) - }) - }) - }) - }) - - describe('> cvtNehubaConfigToNavigationObj', () => { - describe('> returns default obj when input is malformed', () => { - it('> if no arg is provided', () => { - - const obj = cvtNehubaConfigToNavigationObj() - expect(obj).toEqual(defaultNavigationObject) - }) - it('> if null or undefined is provided', () => { - - const obj = cvtNehubaConfigToNavigationObj(null) - expect(obj).toEqual(defaultNavigationObject) - - const obj2 = cvtNehubaConfigToNavigationObj(undefined) - expect(obj2).toEqual(defaultNavigationObject) - }) - it('> if malformed', () => { - - const obj = cvtNehubaConfigToNavigationObj(dummyTmpl2) - expect(obj).toEqual(defaultNavigationObject) - - const obj2 = cvtNehubaConfigToNavigationObj({}) - expect(obj2).toEqual(defaultNavigationObject) - }) - }) - it('> converts nehubaConfig object to navigation object', () => { - const obj = cvtNehubaConfigToNavigationObj(dummyTmpl2.nehubaConfig.dataset.initialNgState) - expect(obj).toEqual(defaultNavigationObject) - }) - }) - -}) diff --git a/src/state/effects/viewerState.useEffect.ts b/src/state/effects/viewerState.useEffect.ts deleted file mode 100644 index 7c590f0c46922146b5c1b8471d219b24e5a335e5..0000000000000000000000000000000000000000 --- a/src/state/effects/viewerState.useEffect.ts +++ /dev/null @@ -1,466 +0,0 @@ -import { Injectable, OnDestroy } from "@angular/core"; -import { Actions, Effect, ofType } from "@ngrx/effects"; -import { Action, select, Store } from "@ngrx/store"; -import { Observable, Subscription, of, merge } from "rxjs"; -import { distinctUntilChanged, filter, map, shareReplay, withLatestFrom, switchMap, mapTo, startWith, catchError } from "rxjs/operators"; -import { FETCHED_TEMPLATE, IavRootStoreInterface, SELECT_PARCELLATION, SELECT_REGIONS, generalActionError } from "src/services/stateStore.service"; -import { TemplateCoordinatesTransformation } from "src/services/templateCoordinatesTransformation.service"; -import { CLEAR_STANDALONE_VOLUMES } from "src/services/state/viewerState.store"; -import { viewerStateToggleRegionSelect, viewerStateHelperSelectParcellationWithId, viewerStateSelectTemplateWithId, viewerStateNavigateToRegion, viewerStateSelectedTemplateSelector, viewerStateFetchedTemplatesSelector, viewerStateNewViewer, viewerStateSelectedParcellationSelector, viewerStateNavigationStateSelector, viewerStateSelectTemplateWithName, viewerStateSelectedRegionsSelector, viewerStateSelectAtlas } from "src/services/state/viewerState.store.helper"; -import { ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState/selectors"; -import { ngViewerActionClearView } from "src/services/state/ngViewerState/actions"; -import { PureContantService } from "src/util"; -import { CONST } from 'common/constants' -import { viewerStateFetchedAtlasesSelector, viewerStateGetSelectedAtlas } from "src/services/state/viewerState/selectors"; -import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions"; -import { cvtNavigationObjToNehubaConfig } from 'src/viewerModule/nehuba/util' -import { getPosFromRegion } from "src/util/siibraApiConstants/fn"; - -const defaultPerspectiveZoom = 1e6 -const defaultZoom = 1e6 - -export const defaultNavigationObject = { - orientation: [0, 0, 0, 1], - perspectiveOrientation: [0.5, -0.5, -0.5, 0.5], - perspectiveZoom: defaultPerspectiveZoom, - zoom: defaultZoom, - position: [0, 0, 0], - positionReal: true -} - -export const defaultNehubaConfigObject = { - perspectiveOrientation: [0.5, -0.5, -0.5, 0.5], - perspectiveZoom: 1e6, - navigation: { - pose: { - position: { - voxelCoordinates: [0, 0, 0], - voxelSize: [1,1,1] - }, - orientation: [0, 0, 0, 1], - }, - zoomFactor: defaultZoom - } -} - -export function cvtNehubaConfigToNavigationObj(nehubaConfig?){ - const { - navigation, - perspectiveOrientation = defaultNavigationObject.perspectiveOrientation, - perspectiveZoom = defaultNavigationObject.perspectiveZoom - } = nehubaConfig || {} - const { pose, zoomFactor = 1e6 } = navigation || {} - const { position, orientation = [0, 0, 0, 1] } = pose || {} - const { voxelSize = [1, 1, 1], voxelCoordinates = [0, 0, 0] } = position || {} - - return { - orientation, - perspectiveOrientation: perspectiveOrientation, - perspectiveZoom: perspectiveZoom, - zoom: zoomFactor, - position: [0, 1, 2].map(idx => voxelSize[idx] * voxelCoordinates[idx]), - positionReal: true - } -} - -@Injectable({ - providedIn: 'root', -}) - -export class ViewerStateControllerUseEffect implements OnDestroy { - - private subscriptions: Subscription[] = [] - - private selectedRegions$: Observable<any[]> - - @Effect() - public init$ = this.pureService.initFetchTemplate$.pipe( - map(fetchedTemplate => { - return { - type: FETCHED_TEMPLATE, - fetchedTemplate, - } - }), - ) - - @Effect() - public onSelectAtlasSelectTmplParc$ = this.actions$.pipe( - ofType(viewerStateSelectAtlas.type), - switchMap(action => this.pureService.allFetchingReady$.pipe( - filter(v => !!v), - mapTo(action) - )), - withLatestFrom( - this.store$.pipe( - select(viewerStateFetchedTemplatesSelector), - startWith([]) - ), - this.store$.pipe( - select(viewerStateFetchedAtlasesSelector), - startWith([]) - ) - ), - map(([action, fetchedTemplates, fetchedAtlases ])=> { - - const { atlas: atlasObj } = action as any - const atlas = fetchedAtlases.find(a => a['@id'] === atlasObj['@id']) - if (!atlas) { - return generalActionError({ - message: CONST.ATLAS_NOT_FOUND - }) - } - /** - * selecting atlas means selecting the first available templateSpace - */ - const targetTmplSpcId = atlasObj['template']?.['@id'] - const templateTobeSelected = ( - targetTmplSpcId - && atlas.templateSpaces.find(t => t['@id'] === targetTmplSpcId) - ) || atlas.templateSpaces[0] - - const templateSpaceId = templateTobeSelected['@id'] - const atlasTmpl = atlas.templateSpaces.find(t => t['@id'] === templateSpaceId) - - const templateSelected = fetchedTemplates.find(t => templateSpaceId === t['@id']) - if (!templateSelected) { - return generalActionError({ - message: CONST.TEMPLATE_NOT_FOUND - }) - } - - const atlasParcs = atlasTmpl.availableIn - .map(availP => atlas.parcellations.find(p => availP['@id'] === p['@id'])) - .filter(fullP => !!fullP) - const atlasParc = atlasParcs.find(p => { - if (!p.baseLayer) return false - if (p['@version']) { - return !p['@version']['@next'] - } - return true - }) || templateSelected.parcellations[0] - const parcellationId = atlasParc && atlasParc['@id'] - const parcellationSelected = parcellationId && templateSelected.parcellations.find(p => p['@id'] === parcellationId) - return viewerStateNewViewer({ - selectTemplate: templateSelected, - selectParcellation: parcellationSelected - }) - }) - ) - - - @Effect() - public selectParcellation$: Observable<any> - - private selectTemplateIntent$: Observable<any> = merge( - this.actions$.pipe( - ofType(viewerStateSelectTemplateWithId.type), - map(({ payload, config }) => { - return { - templateId: payload['@id'], - parcellationId: config && config['selectParcellation'] && config['selectParcellation']['@id'] - } - }) - ), - this.actions$.pipe( - ofType(viewerStateSelectTemplateWithName), - withLatestFrom(this.store$.pipe( - select(viewerStateFetchedTemplatesSelector) - )), - map(([ action, fetchedTemplates ]) => { - const templateName = (action as any).payload.name - const foundTemplate = fetchedTemplates.find(t => t.name === templateName) - return foundTemplate && foundTemplate['@id'] - }), - filter(v => !!v), - map(templateId => { - return { templateId, parcellationId: null } - }) - ) - ) - - @Effect() - public selectTemplate$: Observable<any> = this.selectTemplateIntent$.pipe( - withLatestFrom( - this.store$.pipe( - select(viewerStateFetchedTemplatesSelector) - ), - this.store$.pipe( - select(viewerStateSelectedParcellationSelector) - ) - ), - map(([ { templateId, parcellationId }, fetchedTemplates, parcellationSelected ]) => { - /** - * find the correct template & parcellation from their IDs - */ - - /** - * for template, just look for the new id in fetched templates - */ - const newTemplateTobeSelected = fetchedTemplates.find(t => t['@id'] === templateId) - if (!newTemplateTobeSelected) { - return { - selectTemplate: null, - selectParcellation: null, - errorMessage: `Selected templateId ${templateId} not found.` - } - } - - /** - * for parcellation, - * if new parc id is defined, try to find the corresponding parcellation in the new template - * if above fails, try to find the corresponding parcellation of the currently selected parcellation - * if the above fails, select the first parcellation in the new template - */ - const selectParcellationWithTemplate = (parcellationId && newTemplateTobeSelected['parcellations'].find(p => p['@id'] === parcellationId)) - || (parcellationSelected && parcellationSelected['@id'] && newTemplateTobeSelected['parcellations'].find(p => p['@id'] === parcellationSelected['@id'])) - || newTemplateTobeSelected.parcellations[0] - - return { - selectTemplate: newTemplateTobeSelected, - selectParcellation: selectParcellationWithTemplate - } - }), - withLatestFrom( - this.store$.pipe( - select(viewerStateSelectedTemplateSelector), - startWith(null as any), - ), - this.store$.pipe( - select(viewerStateNavigationStateSelector), - startWith(null as any), - ) - ), - switchMap(([{ selectTemplate, selectParcellation, errorMessage }, lastSelectedTemplate, navigation]) => { - /** - * if selectTemplate is undefined (cannot find template with id) - */ - if (errorMessage) { - return of(generalActionError({ - message: errorMessage || 'Switching template error.', - })) - } - /** - * if there were no template selected last - * simply return selectTemplate object - */ - if (!lastSelectedTemplate) { - return of(viewerStateNewViewer({ - selectParcellation, - selectTemplate, - })) - } - - /** - * if there were template selected last, extract navigation info - */ - const previousNavigation = (navigation && Object.keys(navigation).length > 0 && navigation) || cvtNehubaConfigToNavigationObj(lastSelectedTemplate.nehubaConfig?.dataset?.initialNgState) - return this.coordinatesTransformation.getPointCoordinatesForTemplate(lastSelectedTemplate.name, selectTemplate.name, previousNavigation.position).pipe( - map(({ status, result }) => { - - /** - * if getPointCoordinatesForTemplate returns error, simply load the temp/parc - */ - if (status === 'error') { - return viewerStateNewViewer({ - selectParcellation, - selectTemplate, - }) - } - - /** - * otherwise, copy the nav state to templateSelected - * deepclone of json object is required, or it will mutate the fetchedTemplate - * setting navigation sometimes creates a race con, as creating nehubaViewer is not sync - */ - const deepCopiedState = JSON.parse(JSON.stringify(selectTemplate)) - const initialNgState = deepCopiedState.nehubaConfig.dataset.initialNgState - - const newInitialNgState = cvtNavigationObjToNehubaConfig({ - ...previousNavigation, - position: result - }, initialNgState) - - /** - * mutation of initialNgState is expected here - */ - deepCopiedState.nehubaConfig.dataset.initialNgState = { - ...initialNgState, - ...newInitialNgState - } - - return viewerStateNewViewer({ - selectTemplate: deepCopiedState, - selectParcellation, - }) - }) - ) - }) - ) - - @Effect() - public toggleRegionSelection$: Observable<any> - - @Effect() - public navigateToRegion$: Observable<any> - - @Effect() - public onTemplateSelectClearStandAloneVolumes$: Observable<any> - - @Effect() - public onTemplateSelectUnsetAllClearQueues$: Observable<any> = this.store$.pipe( - select(viewerStateSelectedTemplateSelector), - withLatestFrom(this.store$.pipe( - select(ngViewerSelectorClearViewEntries) - )), - map(([_, clearViewQueue]) => { - const newVal = {} - for (const key of clearViewQueue) { - newVal[key] = false - } - return ngViewerActionClearView({ - payload: newVal - }) - }) - ) - - constructor( - private actions$: Actions, - private store$: Store<IavRootStoreInterface>, - private pureService: PureContantService, - private coordinatesTransformation: TemplateCoordinatesTransformation - ) { - const viewerState$ = this.store$.pipe( - select('viewerState'), - shareReplay(1), - ) - - this.selectedRegions$ = viewerState$.pipe( - select('regionsSelected'), - distinctUntilChanged(), - ) - - this.onTemplateSelectClearStandAloneVolumes$ = this.store$.pipe( - select(viewerStateSelectedTemplateSelector), - distinctUntilChanged(), - mapTo({ type: CLEAR_STANDALONE_VOLUMES }) - ) - - /** - * merge all sources of selecting parcellation into parcellation id - */ - this.selectParcellation$ = merge( - - /** - * listening on action - */ - - this.actions$.pipe( - ofType(viewerStateHelperSelectParcellationWithId.type), - map(({ payload }) => payload['@id']) - ), - - ).pipe( - withLatestFrom(viewerState$.pipe( - select('templateSelected'), - )), - map(([id, templateSelected]) => { - const { parcellations: availableParcellations } = templateSelected - const newParcellation = availableParcellations.find(t => t['@id'] === id) - if (!newParcellation) { - return generalActionError({ - message: 'Selected parcellation not found.' - }) - } - return { - type: SELECT_PARCELLATION, - selectParcellation: newParcellation, - } - }) - ) - - this.navigateToRegion$ = this.actions$.pipe( - ofType(viewerStateNavigateToRegion), - map(action => action.payload?.region), - withLatestFrom( - this.store$.pipe( - select(viewerStateGetSelectedAtlas) - ), - this.store$.pipe( - select(viewerStateSelectedTemplateSelector) - ), - this.store$.pipe( - select(viewerStateSelectedParcellationSelector) - ) - ), - switchMap(([ region, selectedAtlas, selectedTemplate, selectedParcellation ]) => { - if (!region || !selectedAtlas || !selectedTemplate || !selectedParcellation) { - return of( - generalActionError({ - message: `Go to region: region / atlas / template / parcellation not defined.` - }) - ) - } - return this.pureService.getRegionDetail(selectedAtlas['@id'], selectedParcellation['@id'], selectedTemplate['@id'], region).pipe( - map(regDetail => { - const position = getPosFromRegion(regDetail) - if (!position) throw new Error(`region does not have props defined!`) - - return viewerStateChangeNavigation({ - navigation: { - position, - animation: {}, - } - }) - }), - catchError((err) => of( - generalActionError({ - message: `Fetching region detail error: ${err}` - }) - )) - ) - }), - ) - - this.toggleRegionSelection$ = this.actions$.pipe( - ofType(viewerStateToggleRegionSelect.type), - withLatestFrom(this.selectedRegions$), - map(([action, regionsSelected]) => { - - const { payload = {} } = action as ViewerStateAction - const { region } = payload - - /** - * if region does not have labelIndex (not tree leaf), for now, return error - */ - if (!region.labelIndex) { - return generalActionError({ - message: 'Currently, only regions at the lowest hierarchy can be selected.' - }) - } - - /** - * if the region is already selected, deselect it - * if the region is not yet selected, deselect any existing region, and select this region - */ - const roiIsSelected = !!regionsSelected.find(r => r.name === region.name) - return { - type: SELECT_REGIONS, - selectRegions: roiIsSelected - ? [] - : [ region ] - } - }), - ) - } - - public ngOnDestroy() { - while (this.subscriptions.length > 0) { - this.subscriptions.pop().unsubscribe() - } - } -} - -interface ViewerStateAction extends Action { - payload: any - config: any -} diff --git a/src/state/index.ts b/src/state/index.ts index 778709a1c7cdd5a85c71824a18b8c2f1aff1df07..3eba9c31857ad2db855c3c9c8e4c461c8fd488ee 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1,5 +1,91 @@ +import { ActionReducer, StoreModule } from "@ngrx/store" + export { StateModule } from "./state.module" + +import * as atlasSelection from "./atlasSelection" +import * as annotation from "./annotations" +import * as userInterface from "./userInterface" +import * as atlasAppearance from "./atlasAppearance" +import * as plugins from "./plugins" +import * as userInteraction from "./userInteraction" +import * as userPreference from "./userPreference" + export { - ViewerStateControllerUseEffect, - cvtNehubaConfigToNavigationObj, -} from "./effects/viewerState.useEffect" \ No newline at end of file + atlasSelection, + annotation, + userInterface, + atlasAppearance, + plugins, + userInteraction, + userPreference, +} + +export * as generalActions from "./actions" + +function debug(reducer: ActionReducer<any>): ActionReducer<any> { + return function(state, action) { + console.log('state', state); + console.log('action', action); + + return reducer(state, action); + }; +} + +function generalApplyStateReducer(reducer: ActionReducer<MainState>): ActionReducer<MainState> { + return function(_state, action) { + let state = _state + if (action.type === generalApplyState.type) { + state = JSON.parse( + JSON.stringify( + (action as any).state + ) + ) + } + return reducer(state, action) + } +} + +export const RootStoreModule = StoreModule.forRoot({ + [userPreference.nameSpace]: userPreference.reducer, + [atlasSelection.nameSpace]: atlasSelection.reducer, + [userInterface.nameSpace]: userInterface.reducer, + [userInteraction.nameSpace]: userInteraction.reducer, + [annotation.nameSpace]: annotation.reducer, + [plugins.nameSpace]: plugins.reducer, + [atlasAppearance.nameSpace]: atlasAppearance.reducer, +},{ + metaReducers: [ + generalApplyStateReducer, + // debug, + ] +}) + +/** + * + * We have to use a function here. At import time, *.Effect(s) + * would not yet be defined. + * + * @returns Effects from state + */ +export function getStoreEffects() { + return [ + plugins.Effects, + atlasSelection.Effect, + userInterface.Effects, + ] +} + +import { MainState } from "./const" +import { generalApplyState } from "./actions" + +export { MainState } + +export const defaultState: MainState = { + [userPreference.nameSpace]: userPreference.defaultState, + [atlasSelection.nameSpace]: atlasSelection.defaultState, + [userInterface.nameSpace]: userInterface.defaultState, + [userInteraction.nameSpace]: userInteraction.defaultState, + [annotation.nameSpace]: annotation.defaultState, + [plugins.nameSpace]: plugins.defaultState, + [atlasAppearance.nameSpace]: atlasAppearance.defaultState, +} diff --git a/src/state/plugins/actions.ts b/src/state/plugins/actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..759c7523082503c38eaed07b9ff92f34b1b3e775 --- /dev/null +++ b/src/state/plugins/actions.ts @@ -0,0 +1,9 @@ +import { createAction, props } from "@ngrx/store"; +import { nameSpace } from "./const" + +export const clearInitManifests = createAction( + `${nameSpace} clearInitManifests`, + props<{ + nameSpace: string + }>() +) diff --git a/src/state/plugins/const.ts b/src/state/plugins/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..7b004e658ddc8e803a00c892e32a82a6a57499be --- /dev/null +++ b/src/state/plugins/const.ts @@ -0,0 +1,2 @@ +export const nameSpace = `[state.plugins]` +export const INIT_MANIFEST_SRC = `__INIT_MANFEST_SRC__` diff --git a/src/state/plugins/effects.spec.ts b/src/state/plugins/effects.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..98d4d78ebcba3ec7253691f5e4b5fcd89d3a4ff4 --- /dev/null +++ b/src/state/plugins/effects.spec.ts @@ -0,0 +1,130 @@ +import { fakeAsync, TestBed, tick } from "@angular/core/testing"; +import { HttpClientModule, HTTP_INTERCEPTORS, HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpHeaders } from "@angular/common/http"; +import { Effects } from "./effects"; +import { Observable, of } from "rxjs"; +import { Action } from "@ngrx/store"; +import { provideMockActions } from "@ngrx/effects/testing"; +import { MockStore, provideMockStore } from "@ngrx/store/testing"; +import { Injectable } from "@angular/core"; +import { getRandomHex } from 'common/util' +import { AngularMaterialModule } from "src/sharedModules"; +import { hot } from "jasmine-marbles"; +import * as actions from "./actions" +import { INIT_MANIFEST_SRC } from "./const" +import { DialogService } from "src/services/dialogService.service"; +import { selectors } from "."; +import * as constants from "./const" + +const actions$: Observable<Action> = of({type: 'TEST'}) + +const manifest = { + name: getRandomHex(), + templateURL: 'http://localhost:12345/template.html', + scriptURL: 'http://localhost:12345/script.js' +} + +const template = getRandomHex() +const script = getRandomHex() + +@Injectable() +class HTTPInterceptorClass implements HttpInterceptor{ + intercept(req: HttpRequest<any>, next: HttpHandler):Observable<HttpEvent<any>>{ + if(req.url.indexOf('http://localhost:12345') >= 0) { + if (req.url.indexOf('manifest.json') >= 0) { + + const headers = new HttpHeaders() + headers.set('content-type', 'application/json') + return of(new HttpResponse({ + status: 200, + body: manifest, + headers + })) + } + + if (req.url.indexOf('template.html') >= 0) { + + const headers = new HttpHeaders() + headers.set('content-type', 'text/html') + return of(new HttpResponse({ + status: 200, + body: template, + headers + })) + } + + if (req.url.indexOf('script.js') >= 0) { + + const headers = new HttpHeaders() + headers.set('content-type', 'application/javascript') + return of(new HttpResponse({ + status: 200, + body: script, + headers + })) + } + } + return next.handle(req) + } +} + +@Injectable() +class MockPluginService{ + public launchNewWidget(arg) { + console.log('launch new widget') + } +} + +// describe('pluginUseEffect.ts', () => { + +// let spy: jasmine.Spy +// let mockStore: MockStore +// beforeEach(() => { +// TestBed.configureTestingModule({ +// imports: [ +// HttpClientModule, +// AngularMaterialModule +// ], +// providers: [ +// Effects, +// provideMockActions(() => actions$), +// provideMockStore(), +// { +// provide: HTTP_INTERCEPTORS, +// useClass: HTTPInterceptorClass, +// multi: true +// }, +// { +// provide: PluginServices, +// useClass: MockPluginService +// },{ +// provide: DialogService, +// useValue: { +// getUserConfirm() { +// return Promise.resolve() +// } +// } +// } +// ] +// }) +// mockStore = TestBed.inject(MockStore) +// mockStore.overrideSelector(selectors.initManfests, { [constants.INIT_MANIFEST_SRC]: "http://localhost:12345/manifest.json" }) +// const pluginServices = TestBed.inject(PluginServices) +// spy = spyOn(pluginServices, 'launchNewWidget') +// }) + +// it('initManifests should fetch manifest.json', fakeAsync(() => { +// const effect = TestBed.inject(Effects) +// effect.initManLaunch.subscribe() +// expect( +// effect.initManClear +// ).toBeObservable( +// hot('a', { +// a: actions.clearInitManifests({ +// nameSpace: INIT_MANIFEST_SRC +// }) +// }) +// ) +// tick(16) +// expect(spy).toHaveBeenCalledWith(manifest) +// })) +// }) diff --git a/src/state/plugins/effects.ts b/src/state/plugins/effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f147b9b17f29011441b3c92fb7c92a992cca1a2 --- /dev/null +++ b/src/state/plugins/effects.ts @@ -0,0 +1,64 @@ +import { Injectable } from "@angular/core"; +import { createEffect } from "@ngrx/effects"; +import { select, Store } from "@ngrx/store"; +import { catchError, filter, map, mapTo, switchMap } from "rxjs/operators"; +import * as constants from "./const" +import * as selectors from "./selectors" +import * as actions from "./actions" +import { DialogService } from "src/services/dialogService.service"; +import { NEVER, of } from "rxjs"; +import { PluginService } from "src/plugin/service"; + +@Injectable() +export class Effects{ + + initMan = this.store.pipe( + select(selectors.initManfests), + map(initMan => initMan[constants.INIT_MANIFEST_SRC]), + filter(val => val && val.length > 0), + ) + + private pendingList = new Set<string>() + private launchedList = new Set<string>() + private banList = new Set<string>() + + initManLaunch = createEffect(() => this.initMan.pipe( + switchMap(val => of(...val)), + switchMap( + url => { + if (this.pendingList.has(url)) return NEVER + if (this.launchedList.has(url)) return NEVER + if (this.banList.has(url)) return NEVER + this.pendingList.add(url) + return this.dialogSvc + .getUserConfirm({ + message: `This URL is trying to open a plugin from ${url}. Proceed?` + }) + .then(() => { + this.launchedList.add(url) + return this.svc.launchPlugin(url) + }) + .finally(() => { + this.pendingList.delete(url) + }) + } + ), + catchError(() => of(null)) + ), { dispatch: false }) + + initManClear = createEffect(() => this.initMan.pipe( + mapTo( + actions.clearInitManifests({ + nameSpace: constants.INIT_MANIFEST_SRC + }) + ) + )) + + constructor( + private store: Store, + private dialogSvc: DialogService, + private svc: PluginService, + ){ + + } +} diff --git a/src/state/plugins/index.ts b/src/state/plugins/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..d7324fb2780ab0dbe36ab4968208ecf5a40b45a6 --- /dev/null +++ b/src/state/plugins/index.ts @@ -0,0 +1,5 @@ +export * as selectors from "./selectors" +export * as actions from "./actions" +export { reducer, PluginStore, defaultState } from "./store" +export { Effects } from "./effects" +export { nameSpace, INIT_MANIFEST_SRC } from "./const" \ No newline at end of file diff --git a/src/state/plugins/selectors.ts b/src/state/plugins/selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b70d2e7d5dcc500739a02e53abcf938f2ccc094 --- /dev/null +++ b/src/state/plugins/selectors.ts @@ -0,0 +1,10 @@ +import { createSelector } from "@ngrx/store"; +import { PluginStore } from "./store" +import { nameSpace } from "./const" + +const storeSelector = state => state[nameSpace] as PluginStore + +export const initManfests = createSelector( + storeSelector, + state => state.initManifests +) diff --git a/src/state/plugins/store.ts b/src/state/plugins/store.ts new file mode 100644 index 0000000000000000000000000000000000000000..7283a77a4f74431800af70c0360b0c8dfd2204f1 --- /dev/null +++ b/src/state/plugins/store.ts @@ -0,0 +1,30 @@ +import { createReducer, on } from "@ngrx/store"; +import * as actions from "./actions" + +export type PluginStore = { + initManifests: Record<string, string[]> +} + +export const defaultState: PluginStore = { + initManifests: {} +} + +export const reducer = createReducer( + defaultState, + on( + actions.clearInitManifests, + (state, { nameSpace }) => { + if (!state.initManifests[nameSpace]) return state + const newMan: Record<string, string[]> = {} + const { initManifests } = state + for (const key in initManifests) { + if (key === nameSpace) continue + newMan[key] = initManifests[key] + } + return { + ...state, + initManifests: newMan + } + } + ), +) diff --git a/src/state/userInteraction/actions.ts b/src/state/userInteraction/actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..3df6c1b9780d6d0f9fde7e14e205a0d179445093 --- /dev/null +++ b/src/state/userInteraction/actions.ts @@ -0,0 +1,37 @@ +import { createAction, props } from "@ngrx/store" +import { nameSpace } from "./const" +import { SapiRegionModel, SapiFeatureModel, OpenMINDSCoordinatePoint } from "src/atlasComponents/sapi" + +export const mouseOverAnnotations = createAction( + `${nameSpace} mouseOverAnnotations`, + props<{ + annotations: { + "@id": string + }[] + }>() +) + +export const mouseoverRegions = createAction( + `${nameSpace} mouseoverRegions`, + props<{ + regions: SapiRegionModel[] + }>() +) + +export const mouseoverPosition = createAction( + `${nameSpace} mouseoverPosition`, + props<{ + position: OpenMINDSCoordinatePoint + }>() +) + +export const showFeature = createAction( + `${nameSpace} showFeature`, + props<{ + feature: SapiFeatureModel + }>() +) + +export const clearShownFeature = createAction( + `${nameSpace} clearShownFeature`, +) diff --git a/src/state/userInteraction/const.ts b/src/state/userInteraction/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..322454b4575be2ad44f6f7a251a46fc087db1e0c --- /dev/null +++ b/src/state/userInteraction/const.ts @@ -0,0 +1 @@ +export const nameSpace = `[state.userInteraction]` \ No newline at end of file diff --git a/src/state/userInteraction/effects.ts b/src/state/userInteraction/effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..5d4986ad5965117705e6d1aa869407fabdbd8a65 --- /dev/null +++ b/src/state/userInteraction/effects.ts @@ -0,0 +1,17 @@ +import { Injectable } from "@angular/core"; +import { Actions, createEffect, ofType } from "@ngrx/effects"; +import * as atlasSelectionActions from "../atlasSelection/actions" +import * as userInterface from "../userInterface" +import { mapTo } from "rxjs/operators"; + +@Injectable() +export class Effect { + onStandAloneVolumesExistCloseMatDrawer = createEffect(() => this.action.pipe( + ofType(atlasSelectionActions.clearStandAloneVolumes), + mapTo(userInterface.actions.closeSidePanel()) + )) + + constructor(private action: Actions){ + + } +} \ No newline at end of file diff --git a/src/state/userInteraction/index.ts b/src/state/userInteraction/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..44d760341b10d264fcdda4ee28ad157b760a705b --- /dev/null +++ b/src/state/userInteraction/index.ts @@ -0,0 +1,5 @@ +export { Effect } from "./effects" +export { nameSpace } from "./const" +export * as actions from "./actions" +export * as selectors from "./selectors" +export { reducer, UserInteraction, defaultState } from "./store" \ No newline at end of file diff --git a/src/state/userInteraction/selectors.ts b/src/state/userInteraction/selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f43a1a4c56fd0188d90c4fa4fb37f491daa35f3 --- /dev/null +++ b/src/state/userInteraction/selectors.ts @@ -0,0 +1,20 @@ +import { createSelector } from "@ngrx/store"; +import { nameSpace } from "./const" +import { UserInteraction } from "./store"; + +const selectStore = state => state[nameSpace] as UserInteraction + +export const mousingOverRegions = createSelector( + selectStore, + state => state.mouseoverRegions +) + +export const selectedFeature = createSelector( + selectStore, + state => state.selectedFeature +) + +export const mousingOverPosition = createSelector( + selectStore, + state => state.mouseoverPosition +) diff --git a/src/state/userInteraction/store.ts b/src/state/userInteraction/store.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab75700289c279969ac2bba0eaafe3a6da3286e4 --- /dev/null +++ b/src/state/userInteraction/store.ts @@ -0,0 +1,55 @@ +import { createReducer, on } from "@ngrx/store"; +import { SapiRegionModel, SapiFeatureModel, OpenMINDSCoordinatePoint } from "src/atlasComponents/sapi"; +import * as actions from "./actions" + +export type UserInteraction = { + mouseoverRegions: SapiRegionModel[] + selectedFeature: SapiFeatureModel + mouseoverPosition: OpenMINDSCoordinatePoint +} + +export const defaultState: UserInteraction = { + selectedFeature: null, + mouseoverRegions: [], + mouseoverPosition: null +} + +export const reducer = createReducer( + defaultState, + on( + actions.mouseoverRegions, + (state, { regions }) => { + return { + ...state, + mouseoverRegions: regions + } + } + ), + on( + actions.showFeature, + (state, { feature }) => { + return { + ...state, + selectedFeature: feature + } + } + ), + on( + actions.clearShownFeature, + state => { + return { + ...state, + selectedFeature: null + } + } + ), + on( + actions.mouseoverPosition, + (state, { position }) => { + return { + ...state, + mouseoverPosition: position + } + } + ) +) diff --git a/src/state/userInterface/actions.ts b/src/state/userInterface/actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..e423f261e6ef1ee6ffb35a9f0464eda261153f35 --- /dev/null +++ b/src/state/userInterface/actions.ts @@ -0,0 +1,60 @@ +import { TemplateRef } from "@angular/core"; +import { MatBottomSheetConfig } from "@angular/material/bottom-sheet"; +import { MatSnackBarConfig } from "@angular/material/snack-bar"; +import { createAction, props } from "@ngrx/store"; +import { nameSpace, PanelMode } from "./const" + + +export const openSidePanel = createAction( + `${nameSpace} openSidePanel` +) + +export const closeSidePanel = createAction( + `${nameSpace} closeSidePanel` +) + +export const expandSidePanelDetailView = createAction( + `${nameSpace} expandDetailView` +) + +export const showBottomSheet = createAction( + `${nameSpace} showBottomSheet`, + props<{ + template: TemplateRef<any> + config?: MatBottomSheetConfig + }>() +) + +export const snackBarMessage = createAction( + `${nameSpace} snackBarMessage`, + props<{ + message: string + config?: MatSnackBarConfig + }>() +) + + +export const setPanelMode = createAction( + `${nameSpace} setPanelMode`, + props<{ + panelMode: PanelMode + }>() +) + +export const cyclePanelMode = createAction( + `${nameSpace} cyclePanelMode` +) + +export const toggleMaximiseView = createAction( + `${nameSpace} toggleMaximiseView`, + props<{ + targetIndex: number + }>() +) + +export const setPanelOrder = createAction( + `${nameSpace} setPanelOrder`, + props<{ + order: string + }>() +) \ No newline at end of file diff --git a/src/state/userInterface/const.ts b/src/state/userInterface/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..929102239aefb764442a329351c02542a293fe8b --- /dev/null +++ b/src/state/userInterface/const.ts @@ -0,0 +1,5 @@ +export const nameSpace = `[state.ui]` +export type PanelMode = 'FOUR_PANEL' +| 'V_ONE_THREE' +| 'H_ONE_THREE' +| 'SINGLE_PANEL' \ No newline at end of file diff --git a/src/state/userInterface/effects.ts b/src/state/userInterface/effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a213f60a78cb2d648badd77d56be1f4a1e1e65a --- /dev/null +++ b/src/state/userInterface/effects.ts @@ -0,0 +1,118 @@ +import { Injectable } from "@angular/core"; +import { MatBottomSheet, MatBottomSheetRef } from "@angular/material/bottom-sheet"; +import { MatSnackBar } from "@angular/material/snack-bar"; +import { Actions, createEffect, ofType } from "@ngrx/effects"; +import { select, Store } from "@ngrx/store"; +import { of } from "rxjs"; +import { filter, map, mapTo, pairwise, startWith, switchMap, tap, withLatestFrom } from "rxjs/operators"; +import { generalActionError } from "../actions"; +import { userInterface } from ".."; +import { selectors } from "../atlasSelection" +import * as actions from "./actions" + +@Injectable() +export class Effects{ + + freshRegionSelect = this.store.pipe( + select(selectors.selectedRegions), + map(selReg => selReg.length), + startWith(0), + pairwise(), + filter(([prev, curr]) => prev === 0 && curr > 0), + ) + + onFreshRegionSelectSidePanelOpen = createEffect(() => this.freshRegionSelect.pipe( + mapTo(actions.openSidePanel()), + )) + + onFreshRegionSelectSidePanelDetailExpand = createEffect(() => this.freshRegionSelect.pipe( + mapTo(actions.expandSidePanelDetailView()) + )) + + onGeneralError = createEffect(() => this.action.pipe( + ofType(generalActionError.type), + tap(payload => { + this.snackbar.open( + (payload as any)?.message || `Error: cannot complete your action`, + 'Dismiss', + { duration: 5000 } + ) + }) + ), { dispatch: false }) + + onShowBottomSheet = createEffect(() => this.action.pipe( + ofType(actions.showBottomSheet), + tap(({ template, config }) => { + + if (this.bottomSheetRef) { + this.bottomSheetRef.dismiss() + } + this.bottomSheetRef = this.bottomsheet.open( + template, + config + ) + this.bottomSheetRef.afterDismissed().subscribe(() => this.bottomSheetRef = null) + }) + ), { dispatch: false }) + + onSnackbarMessage = createEffect(() => this.action.pipe( + ofType(actions.snackBarMessage), + tap(({ message, config }) => { + const _config = config || { duration: 5000 } + this.snackbar.open(message, "Dismiss", _config) + }) + ), { dispatch: false }) + + onMaximiseView = createEffect(() => this.action.pipe( + ofType(actions.toggleMaximiseView), + withLatestFrom( + this.store.pipe( + select(userInterface.selectors.panelMode), + ) + ), + switchMap(([ { targetIndex }, panelMode ]) => { + const newMode: userInterface.PanelMode = panelMode === "FOUR_PANEL" + ? "SINGLE_PANEL" + : "FOUR_PANEL" + const newOrder = newMode === "FOUR_PANEL" + ? "0123" + : "0123".split("").map(v => ((Number(v) + targetIndex) % 4).toString()).join("") + return of( + userInterface.actions.setPanelMode({ + panelMode: newMode + }), + userInterface.actions.setPanelOrder({ + order: newOrder + }) + ) + }) + )) + + onCycleView = createEffect(() => this.action.pipe( + ofType(userInterface.actions.cyclePanelMode), + withLatestFrom( + this.store.pipe( + select(userInterface.selectors.panelMode) + ), + this.store.pipe( + select(userInterface.selectors.panelOrder) + ), + ), + filter(([_, panelMode, _1]) => panelMode === "SINGLE_PANEL"), + map(([_, _1, panelOrder]) => userInterface.actions.setPanelOrder({ + order: [ + ...panelOrder.split('').slice(1), + panelOrder[0] + ].join('') + })) + )) + + private bottomSheetRef: MatBottomSheetRef + constructor( + private store: Store, + private action: Actions, + private bottomsheet: MatBottomSheet, + private snackbar: MatSnackBar, + ){ + } +} diff --git a/src/state/userInterface/index.ts b/src/state/userInterface/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..382825ba33d4ae79a439f9e8cce8fc909c020bf5 --- /dev/null +++ b/src/state/userInterface/index.ts @@ -0,0 +1,5 @@ +export * as actions from "./actions" +export * as selectors from "./selectors" +export { nameSpace, PanelMode } from "./const" +export { reducer, UiStore, defaultState } from "./store" +export { Effects } from "./effects" diff --git a/src/state/userInterface/selectors.ts b/src/state/userInterface/selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..b9b3001d1435f7545e07b7d64cfd63e8f545d0b8 --- /dev/null +++ b/src/state/userInterface/selectors.ts @@ -0,0 +1,15 @@ +import { createSelector } from "@ngrx/store"; +import { nameSpace } from "./const" +import { UiStore } from "./store" + +const selectStore = state => state[nameSpace] as UiStore + +export const panelMode = createSelector( + selectStore, + state => state.panelMode +) + +export const panelOrder = createSelector( + selectStore, + state => state.panelOrder +) diff --git a/src/state/userInterface/store.ts b/src/state/userInterface/store.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc854e820d61e94cbee9b5b95f988dbe9db0ec58 --- /dev/null +++ b/src/state/userInterface/store.ts @@ -0,0 +1,39 @@ +import { createReducer, on } from "@ngrx/store"; +import * as actions from "./actions" +import { PanelMode } from "./const" + +export type UiStore = { + panelMode: PanelMode + panelOrder: string // permutation of 0123 + octantRemoval: boolean + showDelineation: boolean +} + +export const defaultState: UiStore = { + panelMode: 'FOUR_PANEL', + panelOrder: '0123', + octantRemoval: false, + showDelineation: true, +} + +export const reducer = createReducer( + defaultState, + on( + actions.setPanelMode, + (state, { panelMode }) => { + return { + ...state, + panelMode + } + } + ), + on( + actions.setPanelOrder, + (state, { order }) => { + return { + ...state, + panelOrder: order + } + } + ) +) diff --git a/src/state/userInterface/ui.ts b/src/state/userInterface/ui.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/state/userPreference/actions.ts b/src/state/userPreference/actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..ee6f8f3062bde7c4c1e3648db385d55859134f2c --- /dev/null +++ b/src/state/userPreference/actions.ts @@ -0,0 +1,39 @@ +import { createAction, props } from "@ngrx/store" +import { nameSpace, CSP } from "./const" + +export const setAnimationFlag = createAction( + `${nameSpace} setAnimationFlag`, + props<{ + flag: boolean + }>() +) + +export const setGpuLimit = createAction( + `${nameSpace} setGpuLimit`, + props<{ + limit: number + }>() +) + +export const useMobileUi = createAction( + `${nameSpace} setUseMobileUi`, + props<{ + flag: boolean + }>() +) + +export const agreeCookie = createAction( + `${nameSpace} agreeCookie` +) + +export const agreeKgTos = createAction( + `${nameSpace} agreeKgTos` +) + +export const updateCsp = createAction( + `${nameSpace} updateCsp`, + props<{ + name: string + csp: CSP + }>() +) \ No newline at end of file diff --git a/src/state/userPreference/const.ts b/src/state/userPreference/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..cd96c117aaac67a50928ea673f60faac73c86b86 --- /dev/null +++ b/src/state/userPreference/const.ts @@ -0,0 +1,9 @@ +export const nameSpace = `[state.userPreference]` + +export const maxGpuLimit = 1e9 +export const minGpuLimit = 1e8 + +export interface CSP{ + 'connect-src'?: string[] + 'script-src'?: string[] +} diff --git a/src/state/userPreference/effects.ts b/src/state/userPreference/effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..29aaf3710126543ac635616b4c075939a5d5167a --- /dev/null +++ b/src/state/userPreference/effects.ts @@ -0,0 +1,48 @@ +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { Actions, createEffect, ofType } from "@ngrx/effects"; +import { map } from "rxjs/operators"; +import { COOKIE_VERSION, KG_TOS_VERSION, LOCAL_STORAGE_CONST } from "src/util/constants"; +import * as actions from "./actions" + +@Injectable() +export class Effects{ + + onUseMobileUi = createEffect(() => this.actions$.pipe( + ofType(actions.useMobileUi), + map(({ flag }) => { + window.localStorage.setItem(LOCAL_STORAGE_CONST.MOBILE_UI, JSON.stringify(flag)) + }) + ), { dispatch: false }) + + onSetGpuLimit = createEffect(() => this.actions$.pipe( + ofType(actions.setGpuLimit), + map(({ limit }) => { + localStorage.setItem(LOCAL_STORAGE_CONST.GPU_LIMIT, limit.toString()) + }) + ), { dispatch: false }) + + onAgreeCookie = createEffect(() => this.actions$.pipe( + ofType(actions.agreeCookie), + map(() => { + localStorage.setItem(LOCAL_STORAGE_CONST.AGREE_COOKIE, COOKIE_VERSION) + }) + ), { dispatch: false }) + + onAgreeKgTos = createEffect(() => this.actions$.pipe( + ofType(actions.agreeKgTos), + map(() => { + localStorage.setItem(LOCAL_STORAGE_CONST.AGREE_KG_TOS, KG_TOS_VERSION) + }) + ), { dispatch: false }) + + // TODO setup on startup get user csp + // this.http.get(`${this.constantSvc.backendUrl}user/pluginPermissions`) + // onStartUpGetCsp + + constructor( + private actions$: Actions, + private http: HttpClient, + ){ + } +} diff --git a/src/state/userPreference/index.ts b/src/state/userPreference/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c7a493f7e1b35fc13e4ab3d552774ddad640b4f --- /dev/null +++ b/src/state/userPreference/index.ts @@ -0,0 +1,4 @@ +export { UserPreference, reducer, defaultState } from "./store" +export { nameSpace } from "./const" +export * as actions from "./actions" +export * as selectors from "./selectors" diff --git a/src/state/userPreference/selectors.ts b/src/state/userPreference/selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..608264fc6ce5d007e81b53fd1fadd25ae4a16dfb --- /dev/null +++ b/src/state/userPreference/selectors.ts @@ -0,0 +1,35 @@ +import { createSelector } from "@ngrx/store" +import { nameSpace } from "./const" +import { UserPreference } from "./store" + +const storeSelector = store => store[nameSpace] as UserPreference + +export const useAnimation = createSelector( + storeSelector, + state => state.useAnimation +) + +export const gpuLimit = createSelector( + storeSelector, + state => state.gpuLimit +) + +export const useMobileUi = createSelector( + storeSelector, + state => state.useMobileUi +) + +export const agreedToCookie = createSelector( + storeSelector, + store => store.agreeCookie +) + +export const agreedToKgTos = createSelector( + storeSelector, + store => store.agreeKgTos +) + +export const userCsp = createSelector( + storeSelector, + store => store.pluginCSP +) diff --git a/src/state/userPreference/store.ts b/src/state/userPreference/store.ts new file mode 100644 index 0000000000000000000000000000000000000000..dbf0be95561efd8992d77a54e6c33db4f6f2c7a8 --- /dev/null +++ b/src/state/userPreference/store.ts @@ -0,0 +1,93 @@ +import { createReducer, on } from "@ngrx/store" +import { COOKIE_VERSION, KG_TOS_VERSION, LOCAL_STORAGE_CONST } from "src/util/constants" +import * as actions from "./actions" +import { maxGpuLimit, CSP } from "./const" + +export const defaultGpuLimit = maxGpuLimit + +export type UserPreference = { + useMobileUi: boolean + gpuLimit: number + useAnimation: boolean + pluginCSP: Record<string, CSP> + + agreeCookie: boolean + agreeKgTos: boolean +} + +export const defaultState: UserPreference = { + useMobileUi: JSON.parse(localStorage.getItem(LOCAL_STORAGE_CONST.MOBILE_UI)), + gpuLimit: Number(localStorage.getItem(LOCAL_STORAGE_CONST.GPU_LIMIT)) || defaultGpuLimit, + useAnimation: !localStorage.getItem(LOCAL_STORAGE_CONST.ANIMATION), + pluginCSP: {}, + + agreeCookie: localStorage.getItem(LOCAL_STORAGE_CONST.AGREE_COOKIE) === COOKIE_VERSION, + agreeKgTos: localStorage.getItem(LOCAL_STORAGE_CONST.AGREE_KG_TOS) === KG_TOS_VERSION, +} + +export const reducer = createReducer( + defaultState, + on( + actions.setAnimationFlag, + (state, { flag }) => { + if (flag) { + localStorage.removeItem(LOCAL_STORAGE_CONST.ANIMATION) + } else { + localStorage.setItem(LOCAL_STORAGE_CONST.ANIMATION, "false") + } + + return { + ...state, + useAnimation: flag + } + } + ), + on( + actions.setGpuLimit, + (state, { limit }) => { + return { + ...state, + gpuLimit: limit + } + } + ), + on( + actions.useMobileUi, + (state, { flag }) => { + return { + ...state, + useMobileUi: flag + } + } + ), + on( + actions.agreeCookie, + state => { + return { + ...state, + agreeCookie: true + } + } + ), + on( + actions.agreeKgTos, + state => { + return { + ...state, + agreeKgTos: true + } + } + ), + on( + actions.updateCsp, + (state, { name, csp }) => { + return { + ...state, + pluginCSP: { + ...state.pluginCSP, + [name]: csp + } + } + } + ) +) diff --git a/src/theme.scss b/src/theme.scss index 87c14ed3d10df20ac5ee476027f0071633be9ba2..8140e544fe37b55173fc22cc6b374c5e559ad2b4 100644 --- a/src/theme.scss +++ b/src/theme.scss @@ -1,5 +1,5 @@ @use 'sass:map'; -@use '~@angular/material' as mat; +@use '@angular/material' as mat; @include mat.core(); @@ -13,9 +13,20 @@ $accent: map-get($color-config, accent); $warn: map-get($color-config, warn); - [iv-custom-comp], - .iv-custom-comp + [sxplr-custom-cmp], + .sxplr-custom-cmp { + color: mat.get-color-from-palette($foreground, text); + + &.hoverable + { + padding: 1rem 1.3rem; + &:hover + { + cursor: pointer; + background-color: mat.get-color-from-palette($background, 100); + } + } &[target="_blank"] { diff --git a/src/ui/config/configCmp/config.component.ts b/src/ui/config/configCmp/config.component.ts index 15f92277c707e22aeeb25cb64dec3e562f907030..494403b61842914fa93cfd377d8aa2ffe907ca5f 100644 --- a/src/ui/config/configCmp/config.component.ts +++ b/src/ui/config/configCmp/config.component.ts @@ -2,17 +2,11 @@ import { Component, OnDestroy, OnInit } from '@angular/core' import { select, Store } from '@ngrx/store'; import { combineLatest, Observable, Subscription } from 'rxjs'; import { debounceTime, distinctUntilChanged, map, startWith } from 'rxjs/operators'; -import { SUPPORTED_PANEL_MODES } from 'src/services/state/ngViewerState.store'; -import { ngViewerActionSetPanelOrder } from 'src/services/state/ngViewerState.store.helper'; -import { VIEWER_CONFIG_ACTION_TYPES, StateInterface as ViewerConfiguration } from 'src/services/state/viewerConfig.store' -import { IavRootStoreInterface } from 'src/services/stateStore.service'; import { isIdentityQuat } from 'src/viewerModule/nehuba/util'; -import {MatSlideToggleChange} from "@angular/material/slide-toggle"; -import {MatSliderChange} from "@angular/material/slider"; -import { PureContantService } from 'src/util'; -import { ngViewerActionSwitchPanelMode } from 'src/services/state/ngViewerState/actions'; -import { ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from 'src/services/state/ngViewerState/selectors'; -import { viewerStateSelectorNavigation } from 'src/services/state/viewerState/selectors'; +import { MatSlideToggleChange } from "@angular/material/slide-toggle"; +import { MatSliderChange } from "@angular/material/slider"; +import { atlasSelection, userPreference, userInterface } from 'src/state'; +import { environment } from "src/environments/environment" const GPU_TOOLTIP = `Higher GPU usage can cause crashes on lower end machines` const ANIMATION_TOOLTIP = `Animation can cause slowdowns in lower end machines` @@ -33,14 +27,25 @@ export class ConfigComponent implements OnInit, OnDestroy { public GPU_TOOLTIP = GPU_TOOLTIP public ANIMATION_TOOLTIP = ANIMATION_TOOLTIP public MOBILE_UI_TOOLTIP = MOBILE_UI_TOOLTIP - public supportedPanelModes = SUPPORTED_PANEL_MODES + + public experimentalFlag = environment.EXPERIMENTAL_FEATURE_FLAG + + public panelModes: Record<string, userInterface.PanelMode> = { + FOUR_PANEL: "FOUR_PANEL", + H_ONE_THREE: "H_ONE_THREE", + SINGLE_PANEL: "SINGLE_PANEL", + V_ONE_THREE: "V_ONE_THREE", + } + /** * in MB */ public gpuLimit$: Observable<number> - public useMobileUI$: Observable<boolean> + public useMobileUI$: Observable<boolean> = this.store.pipe( + select(userPreference.selectors.useMobileUi) + ) public animationFlag$: Observable<boolean> private subscriptions: Subscription[] = [] @@ -56,35 +61,28 @@ export class ConfigComponent implements OnInit, OnDestroy { private viewerObliqueRotated$: Observable<boolean> constructor( - private store: Store<IavRootStoreInterface>, - private pureConstantService: PureContantService, + private store: Store<any>, ) { - this.useMobileUI$ = this.pureConstantService.useTouchUI$ - this.gpuLimit$ = this.store.pipe( - select('viewerConfigState'), - map((config: ViewerConfiguration) => config.gpuLimit), - distinctUntilChanged(), + select(userPreference.selectors.gpuLimit), map(v => v / 1e6), ) this.animationFlag$ = this.store.pipe( - select('viewerConfigState'), - map((config: ViewerConfiguration) => config.animation), + select(userPreference.selectors.useAnimation) ) this.panelMode$ = this.store.pipe( - select(ngViewerSelectorPanelMode), - startWith(SUPPORTED_PANEL_MODES[0]), + select(userInterface.selectors.panelMode) ) this.panelOrder$ = this.store.pipe( - select(ngViewerSelectorPanelOrder), + select(userInterface.selectors.panelOrder), ) this.viewerObliqueRotated$ = this.store.pipe( - select(viewerStateSelectorNavigation), + select(atlasSelection.selectors.navigation), map(navigation => (navigation && navigation.orientation) || [0, 0, 0, 1]), debounceTime(100), map(isIdentityQuat), @@ -115,36 +113,34 @@ export class ConfigComponent implements OnInit, OnDestroy { public toggleMobileUI(ev: MatSlideToggleChange) { const { checked } = ev - this.store.dispatch({ - type: VIEWER_CONFIG_ACTION_TYPES.SET_MOBILE_UI, - payload: { - useMobileUI: checked, - }, - }) + this.store.dispatch( + userPreference.actions.useMobileUi({ + flag: checked + }) + ) } public toggleAnimationFlag(ev: MatSlideToggleChange ) { const { checked } = ev - this.store.dispatch({ - type: VIEWER_CONFIG_ACTION_TYPES.UPDATE_CONFIG, - config: { - animation: checked, - }, - }) + this.store.dispatch( + userPreference.actions.setAnimationFlag({ + flag: checked + }) + ) } public handleMatSliderChange(ev: MatSliderChange) { - this.store.dispatch({ - type: VIEWER_CONFIG_ACTION_TYPES.UPDATE_CONFIG, - config: { - gpuLimit: ev.value * 1e6, - }, - }) + this.store.dispatch( + userPreference.actions.setGpuLimit({ + limit: ev.value * 1e6 + }) + ) } - public usePanelMode(panelMode: string) { + public usePanelMode(panelMode: userInterface.PanelMode) { + this.store.dispatch( - ngViewerActionSwitchPanelMode({ - payload: { panelMode } + userInterface.actions.setPanelMode({ + panelMode }) ) } @@ -160,8 +156,8 @@ export class ConfigComponent implements OnInit, OnDestroy { [arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]] this.store.dispatch( - ngViewerActionSetPanelOrder({ - payload: { panelOrder: arr.join('') } + userInterface.actions.setPanelOrder({ + order: arr.join('') }) ) } diff --git a/src/ui/config/configCmp/config.stories.ts b/src/ui/config/configCmp/config.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1aa0a63fa156ec53b5d48e120d2278840896279 --- /dev/null +++ b/src/ui/config/configCmp/config.stories.ts @@ -0,0 +1,46 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { ConfigModule } from "../module" +import { ConfigComponent } from "./config.component" +import { userPreference, userInterface, atlasSelection } from "src/state" +import { StoreModule } from "@ngrx/store" + +export default { + component: ConfigComponent, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + ConfigModule, + StoreModule.forRoot({ + [userPreference.nameSpace]: userPreference.reducer, + [userInterface.nameSpace]: userInterface.reducer, + [atlasSelection.nameSpace]: atlasSelection.reducer, + }) + ], + providers: [ + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<ConfigComponent> = (args: ConfigComponent, { loaded }) => { + const { experimentalFlag } = args + return ({ + props: { + experimentalFlag + }, + }) +} + +export const Default = Template.bind({}) +Default.args = { + experimentalFlag: true +} +Default.loaders = [ +] \ No newline at end of file diff --git a/src/ui/config/configCmp/config.style.css b/src/ui/config/configCmp/config.style.css index 907391412ce3868cca03f2b38599f2f551238085..9fccc534f7886d65ce63f92ca16c8ecf8fc89c42 100644 --- a/src/ui/config/configCmp/config.style.css +++ b/src/ui/config/configCmp/config.style.css @@ -11,4 +11,16 @@ .onDragOver { background-color: rgba(128,128,128,0.2); +} + +.chunky +{ + width: 100%; + height: 100%; +} + +.uncollapsable +{ + width: 10em; + height: 7em; } \ No newline at end of file diff --git a/src/ui/config/configCmp/config.template.html b/src/ui/config/configCmp/config.template.html index deb625baac125ea4ff0a47915adf349144d31ae1..eab836a149b2f949cd154ea2e49fe572a90dca6b 100644 --- a/src/ui/config/configCmp/config.template.html +++ b/src/ui/config/configCmp/config.template.html @@ -1,15 +1,67 @@ <mat-tab-group> + + <!-- hard ware --> + <mat-tab label="Hardware"> + <!-- wrapper + margin control --> + <div class="sxplr-m-4"> + + <!-- use mobile UI --> + <div class="d-flex mb-2 align-items-center"> + <mat-slide-toggle + [checked]="useMobileUI$ | async" + (change)="toggleMobileUI($event)"> + Enable Mobile UI + </mat-slide-toggle> + <small iav-stop="click mousedown mouseup" [matTooltip]="MOBILE_UI_TOOLTIP" class="ml-2 fas fa-question"></small> + </div> + + <!-- animation toggle --> + <div class="d-flex mb-2 align-items-center"> + <mat-slide-toggle + [checked]="animationFlag$ | async" + (change)="toggleAnimationFlag($event)"> + Enable Animation + </mat-slide-toggle> + <small iav-stop="click mousedown mouseup" [matTooltip]="ANIMATION_TOOLTIP" class="ml-2 fas fa-question"></small> + </div> + + <!-- GPU limit --> + <div class="d-flex flex-row align-items-center justify-content start"> + <label + class="sxplr-m-0 d-inline-block flex-grow-0 flex-shrink-0" + for="gpuLimitSlider"> + GPU Limit + <small iav-stop="click mousedown mouseup" [matTooltip]="GPU_TOOLTIP" class="ml-2 fas fa-question"></small> + </label> + <mat-slider + class="flex-grow-1 flex-shrink-1 ml-2 mr-2" + id="gpuLimitSlider" + name="gpuLimitSlider" + thumbLabel="true" + min="100" + max="1000" + [step]="stepSize" + (change)="handleMatSliderChange($event)" + [value]="gpuLimit$ | async"> + </mat-slider> + <span class="d-inline-block flex-grow-0 flex-shrink-0 w-10em"> + {{ gpuLimit$ | async }} MB + </span> + </div> + </div> + </mat-tab> + <!-- viewer preference --> - <mat-tab *ngIf="false" label="Viewer Preference"> - - <div class="m-2"> + <mat-tab *ngIf="experimentalFlag" label="Viewer Preference"> + + <div class="sxplr-custom-cmp text sxplr-m-2"> <div class="mat-h2"> Rearrange Viewports </div> <div class="mat-h4 text-muted"> Click and drag to rearrange viewport positions </div> - <current-layout class="d-flex w-20em h-15em p-2"> + <current-layout class="d-flex w-20em h-15em sxplr-p-2"> <div matRipple (dragstart)="handleDragStart($event)" @@ -17,11 +69,11 @@ (dragleave)="handleDragLeave($event)" (dragend)="handleDragend($event)" (drop)="handleDrop($event)" - class="w-100 h-100 config-transition" + class="chunky config-transition" cell-i> <div [attr.panel-order]="0" - class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + class="config-transition chunky d-flex align-items-center justify-content-center border" draggable="true"> {{ (panelTexts$ | async)[0] }} </div> @@ -33,11 +85,11 @@ (dragleave)="handleDragLeave($event)" (dragend)="handleDragend($event)" (drop)="handleDrop($event)" - class="w-100 h-100 config-transition" + class="chunky config-transition" cell-ii> <div [attr.panel-order]="1" - class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + class="config-transition chunky d-flex align-items-center justify-content-center border" draggable="true"> {{ (panelTexts$ | async)[1] }} </div> @@ -49,11 +101,11 @@ (dragleave)="handleDragLeave($event)" (dragend)="handleDragend($event)" (drop)="handleDrop($event)" - class="w-100 h-100 config-transition" + class="chunky config-transition" cell-iii> <div [attr.panel-order]="2" - class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + class="config-transition chunky d-flex align-items-center justify-content-center border" draggable="true"> {{ (panelTexts$ | async)[2] }} </div> @@ -65,146 +117,112 @@ (dragleave)="handleDragLeave($event)" (dragend)="handleDragend($event)" (drop)="handleDrop($event)" - class="w-100 h-100 config-transition" + class="chunky config-transition" cell-iv> <div [attr.panel-order]="3" - class="config-transition w-100 h-100 d-flex align-items-center justify-content-center border" + class="config-transition chunky d-flex align-items-center justify-content-center border" draggable="true"> {{ (panelTexts$ | async)[3] }} </div> </div> </current-layout> - <div class="mat-body text-muted font-italic"> + <div class="sxplr-custom-cmp text text-muted font-italic"> Plane designation refers to default orientation (without oblique rotation). </div> </div> <!-- scroll window --> - <div class="m-2"> + <div class="sxplr-m-2 sxplr-custom-cmp text"> <div class="mat-h2"> Select a viewports configuration </div> </div> - <div class="d-flex flex-row flex-nowrap p-2"> + <div class="d-flex flex-row flex-nowrap sxplr-p-2"> + BLA? + <!-- main template --> + <ng-template #panelModeBtnTmpl + let-panelMode="panelMode" + let-previewTmpl="previewTmpl"> + <button + class="sxplr-m-2 sxplr-p-2" + mat-flat-button + (click)="usePanelMode(panelMode)" + [color]="(panelMode$ | async) === panelMode ? 'primary' : null"> + + <div class="uncollapsable"> + + <ng-template [ngTemplateOutlet]="previewTmpl"> + </ng-template> + </div> + </button> + </ng-template> <!-- Four Panel Card --> - <button - class="m-2 p-2" - mat-flat-button - (click)="usePanelMode(supportedPanelModes[0])" - [color]="(panelMode$ | async) === supportedPanelModes[0] ? 'primary' : null"> - <layout-four-panel class="d-block w-10em h-7em"> - <div class="border w-100 h-100" cell-i></div> - <div class="border w-100 h-100" cell-ii></div> - <div class="border w-100 h-100" cell-iii></div> - <div class="border w-100 h-100" cell-iv></div> + <ng-template #layoutFourPanelTmpl> + <layout-four-panel class="d-block chunky"> + <div class="sxplr-border chunky" cell-i></div> + <div class="sxplr-border chunky" cell-ii></div> + <div class="sxplr-border chunky" cell-iii></div> + <div class="sxplr-border chunky" cell-iv></div> </layout-four-panel> - </button> + </ng-template> + <ng-template [ngTemplateOutlet]="panelModeBtnTmpl" + [ngTemplateOutletContext]="{ + panelMode: panelModes.FOUR_PANEL, + previewTmpl: layoutFourPanelTmpl + }"> + </ng-template> <!-- temporarily disabling 1-3 layout --> <!-- horizontal 1 3 card --> <!-- <button - class="m-2 p-2" + class="sxplr-m-2 sxplr-p-2" mat-flat-button - (click)="usePanelMode(supportedPanelModes[1])" - [color]="(panelMode$ | async) === supportedPanelModes[1] ? 'primary' : null"> + (click)="usePanelMode(panelModes.H_ONE_THREE)" + [color]="(panelMode$ | async) === panelModes.H_ONE_THREE ? 'primary' : null"> <layout-horizontal-one-three class="d-block w-10em h-7em"> - <div class="border w-100 h-100" cell-i></div> - <div class="border w-100 h-100" cell-ii></div> - <div class="border w-100 h-100" cell-iii></div> - <div class="border w-100 h-100" cell-iv></div> + <div class="border chunky" cell-i></div> + <div class="border chunky" cell-ii></div> + <div class="border chunky" cell-iii></div> + <div class="border chunky" cell-iv></div> </layout-horizontal-one-three> </button> --> - + <!-- vertical 1 3 card --> <!-- <button - class="m-2 p-2" + class="sxplr-m-2 sxplr-p-2" mat-flat-button - (click)="usePanelMode(supportedPanelModes[2])" - [color]="(panelMode$ | async) === supportedPanelModes[2] ? 'primary' : null"> + (click)="usePanelMode(panelModes.V_ONE_THREE)" + [color]="(panelMode$ | async) === panelModes.V_ONE_THREE ? 'primary' : null"> <layout-vertical-one-three class="d-block w-10em h-7em"> - <div class="border w-100 h-100" cell-i></div> - <div class="border w-100 h-100" cell-ii></div> - <div class="border w-100 h-100" cell-iii></div> - <div class="border w-100 h-100" cell-iv></div> + <div class="border chunky" cell-i></div> + <div class="border chunky" cell-ii></div> + <div class="border chunky" cell-iii></div> + <div class="border chunky" cell-iv></div> </layout-vertical-one-three> </button> --> <!-- single --> - <button - class="m-2 p-2" - mat-flat-button - (click)="usePanelMode(supportedPanelModes[3])" - [color]="(panelMode$ | async) === supportedPanelModes[3] ? 'primary' : null"> + <ng-template #singlePanelTmpl> <layout-single-panel class="d-block w-10em h-7em"> - <div class="border w-100 h-100" cell-i></div> - <div class="border w-100 h-100" cell-ii></div> - <div class="border w-100 h-100" cell-iii></div> - <div class="border w-100 h-100" cell-iv></div> + <div class="border chunky" cell-i></div> + <div class="border chunky" cell-ii></div> + <div class="border chunky" cell-iii></div> + <div class="border chunky" cell-iv></div> </layout-single-panel> - </button> - </div> - </mat-tab> - - <!-- hard ware --> - <mat-tab label="Hardware"> - <!-- wrapper + margin control --> - <div class="m-4"> - - <!-- use mobile UI --> - <div class="d-flex mb-2 align-items-center"> - <mat-slide-toggle - [checked]="useMobileUI$ | async" - (change)="toggleMobileUI($event)"> - Enable Mobile UI - </mat-slide-toggle> - <small iav-stop="click mousedown mouseup" [matTooltip]="MOBILE_UI_TOOLTIP" class="ml-2 fas fa-question"></small> - </div> - - <!-- animation toggle --> - <div class="d-flex mb-2 align-items-center"> - <mat-slide-toggle - [checked]="animationFlag$ | async" - (change)="toggleAnimationFlag($event)"> - Enable Animation - </mat-slide-toggle> - <small iav-stop="click mousedown mouseup" [matTooltip]="ANIMATION_TOOLTIP" class="ml-2 fas fa-question"></small> - </div> - - <!-- GPU limit --> - <div class="d-flex flex-row align-items-center justify-content start"> - <label - class="m-0 d-inline-block flex-grow-0 flex-shrink-0" - for="gpuLimitSlider"> - GPU Limit - <small iav-stop="click mousedown mouseup" [matTooltip]="GPU_TOOLTIP" class="ml-2 fas fa-question"></small> - </label> - <mat-slider - class="flex-grow-1 flex-shrink-1 ml-2 mr-2" - id="gpuLimitSlider" - name="gpuLimitSlider" - thumbLabel="true" - min="100" - max="1000" - [step]="stepSize" - (change)="handleMatSliderChange($event)" - [value]="gpuLimit$ | async"> - </mat-slider> - <span class="d-inline-block flex-grow-0 flex-shrink-0 w-10em"> - {{ gpuLimit$ | async }} MB - </span> - </div> + </ng-template> + <ng-template [ngTemplateOutlet]="panelModeBtnTmpl" + [ngTemplateOutletContext]="{ + panelMode: panelModes.SINGLE_PANEL, + previewTmpl: singlePanelTmpl + }"> + </ng-template> </div> </mat-tab> - - <!-- plugin csp --> - <mat-tab label="Plugin Permission"> - <plugin-csp-controller></plugin-csp-controller> - </mat-tab> </mat-tab-group> diff --git a/src/ui/config/module.ts b/src/ui/config/module.ts index f52b5ffb9e5aded977503089cee9f9e5f3e06d10..55fcded2c2c01aa17ce1bdb3420dc2e6c9634e5e 100644 --- a/src/ui/config/module.ts +++ b/src/ui/config/module.ts @@ -9,7 +9,7 @@ import { ConfigComponent } from "./configCmp/config.component"; imports: [ CommonModule, AngularMaterialModule, - PluginModule, + // PluginModule, LayoutModule, ], declarations: [ diff --git a/src/ui/dialogInfo/const.ts b/src/ui/dialogInfo/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..09aa80f6f979e24b8cd407ec54fcbdfa4c8d3154 --- /dev/null +++ b/src/ui/dialogInfo/const.ts @@ -0,0 +1 @@ +import { InjectionToken } from "@angular/core"; diff --git a/src/ui/dialogInfo/dialog.directive.ts b/src/ui/dialogInfo/dialog.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..1b30c4e59ede96787318fe0f8582a2ee85317072 --- /dev/null +++ b/src/ui/dialogInfo/dialog.directive.ts @@ -0,0 +1,58 @@ +import { Directive, HostListener, Input, TemplateRef } from "@angular/core"; +import { MatDialog, MatDialogConfig } from "@angular/material/dialog"; +import { MatSnackBar } from "@angular/material/snack-bar"; + +type DialogSize = 's' | 'm' | 'l' | 'xl' + +const sizeDict: Record<DialogSize, Partial<MatDialogConfig>> = { + 's': { + width: '25vw', + height: '25vh' + }, + 'm': { + width: '50vw', + height: '50vh' + }, + 'l': { + width: '75vw', + height: '75vh' + }, + 'xl': { + width: '90vw', + height: '90vh' + } +} + +@Directive({ + selector: `[sxplr-dialog]`, + exportAs: 'sxplrDialog', +}) + +export class DialogDirective{ + + @Input('sxplr-dialog') + templateRef: TemplateRef<unknown> + + @Input('sxplr-dialog-size') + size: DialogSize = 'm' + + @Input('sxplr-dialog-data') + data: unknown + + constructor( + private matDialog: MatDialog, + private snackbar: MatSnackBar, + ){ + } + + @HostListener('click') + onClick(){ + if (!this.templateRef) { + return this.snackbar.open(`Cannot show dialog. sxplr-dialog template not provided`) + } + this.matDialog.open(this.templateRef, { + data: this.data, + ...sizeDict[this.size] + }) + } +} \ No newline at end of file diff --git a/src/ui/dialogInfo/index.ts b/src/ui/dialogInfo/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..354dde0cbdafd34674a303cfa52d0dd45aa3487f --- /dev/null +++ b/src/ui/dialogInfo/index.ts @@ -0,0 +1 @@ +export { DialogDirective } from "./dialog.directive" \ No newline at end of file diff --git a/src/ui/dialogInfo/module.ts b/src/ui/dialogInfo/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..de09c12a621f36ca70b46354e7eda5a040802b70 --- /dev/null +++ b/src/ui/dialogInfo/module.ts @@ -0,0 +1,19 @@ +import { NgModule } from "@angular/core"; +import { MatDialogModule } from "@angular/material/dialog"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { DialogDirective } from "./dialog.directive" + +@NgModule({ + imports: [ + MatSnackBarModule, + MatDialogModule, + ], + declarations: [ + DialogDirective, + ], + exports: [ + DialogDirective, + ], +}) + +export class DialogModule{} diff --git a/src/ui/help/about/about.component.ts b/src/ui/help/about/about.component.ts index 77dd5c431ba9ec42fc8911ead646806abeda0832..08c4e1999e5de2ead41f81f49d28957dc3c0d747 100644 --- a/src/ui/help/about/about.component.ts +++ b/src/ui/help/about/about.component.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core' -import { PureContantService } from 'src/util'; @Component({ selector: 'iav-about', @@ -10,14 +9,9 @@ import { PureContantService } from 'src/util'; }) export class AboutCmp { - public contactEmailHref: string = `mailto:${this.constantService.supportEmailAddress}?Subject=[InteractiveAtlasViewer]%20Queries` - public supportEmailAddress: string = this.constantService.supportEmailAddress + public supportEmailAddress: string = `support@ebrains.eu` + public contactEmailHref: string = `mailto:${this.supportEmailAddress}?Subject=[siibra-explorer]%20Queries` - public userDoc: string = this.constantService.docUrl - public repoUrl = this.constantService.repoUrl - - constructor( - private constantService: PureContantService, - ) { - } + public userDoc = `https://siibra-explorer.readthedocs.io/en/latest/` + public repoUrl = `https://github.com/FZJ-INM1-BDA/siibra-explorer` } diff --git a/src/ui/help/helpOnePager/helpOnePager.component.spec.ts b/src/ui/help/helpOnePager/helpOnePager.component.spec.ts index 08f17969c3656fb5d3d73ca8a6adf6270d605eae..ea3ceeba5935b89bb6de1e7e017cdec927fac15e 100644 --- a/src/ui/help/helpOnePager/helpOnePager.component.spec.ts +++ b/src/ui/help/helpOnePager/helpOnePager.component.spec.ts @@ -3,7 +3,6 @@ import { TestBed } from '@angular/core/testing' import { ComponentsModule } from 'src/components' import { AngularMaterialModule } from 'src/sharedModules' import { QuickTourModule } from 'src/ui/quickTour' -import { PureContantService } from 'src/util' import { UtilModule } from 'src/util/util.module' import { HelpOnePager } from './helpOnePager.component' @@ -24,12 +23,6 @@ describe('> helpOnePager.component.ts', () => { declarations: [ HelpOnePager, ], - providers: [ - { - provide: PureContantService, - useValue: {} - } - ] }).compileComponents() }) it('> should render a table', () => { diff --git a/src/ui/help/helpOnePager/helpOnePager.component.ts b/src/ui/help/helpOnePager/helpOnePager.component.ts index f7afe35c9306a196b0c4bc0e20f1e9ee447258c7..fb4d54137af64cc245c56e30beeaea1564417311 100644 --- a/src/ui/help/helpOnePager/helpOnePager.component.ts +++ b/src/ui/help/helpOnePager/helpOnePager.component.ts @@ -1,6 +1,5 @@ import { MatDialog } from '@angular/material/dialog'; -import { Component, Optional } from "@angular/core"; -import { PureContantService } from "src/util"; +import { Component } from "@angular/core"; import { ARIA_LABELS } from 'common/constants' import { HowToCite } from '../howToCite/howToCite.component'; @@ -21,13 +20,9 @@ export class HelpOnePager{ public extQuickStarter: string public userDoc: string constructor( - @Optional() pConstService: PureContantService, private dialog: MatDialog, ){ this.extQuickStarter = `quickstart.html` - if (pConstService) { - this.userDoc = pConstService.docUrl - } } howToCite(){ diff --git a/src/ui/help/helpOnePager/helpOnePager.template.html b/src/ui/help/helpOnePager/helpOnePager.template.html index af0ae2e65b7bd8a6047e026165ad922f98662267..c0789dd0ca3933ad2f1c47c99644ceee28b35a4f 100644 --- a/src/ui/help/helpOnePager/helpOnePager.template.html +++ b/src/ui/help/helpOnePager/helpOnePager.template.html @@ -5,7 +5,7 @@ <a *ngIf="extQuickStarter" [href]="extQuickStarter" target="_blank" - class="position-absolute top-0 right-0" + class="position-absolute tosxplr-p-0 right-0" [matTooltip]="ARIA_LABELS.OPEN_IN_NEW_WINDOW"> <button mat-icon-button color="primary"> diff --git a/src/ui/help/howToCite/howToCite.style.css b/src/ui/help/howToCite/howToCite.style.css index be8e9d87a469b5adc6608260adcbf1780f45206b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/src/ui/help/howToCite/howToCite.style.css +++ b/src/ui/help/howToCite/howToCite.style.css @@ -1,4 +0,0 @@ -:host >>> img -{ - width: 100%; -} \ No newline at end of file diff --git a/src/ui/layerbrowser/index.ts b/src/ui/layerbrowser/index.ts deleted file mode 100644 index e1c19e45596aa009b1c8a86a22b48628dceb991e..0000000000000000000000000000000000000000 --- a/src/ui/layerbrowser/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { LayerBrowserModule } from './layerBrowser.module' - - -export interface INgLayerInterface { - name: string - visible: boolean - source: string - type: string // image | segmentation | etc ... - transform?: [[number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number]] | null - // colormap : string -} diff --git a/src/ui/layerbrowser/layerBrowser.module.ts b/src/ui/layerbrowser/layerBrowser.module.ts deleted file mode 100644 index a634859a0ce3ed07d2d57b2471cc481bbe89e27b..0000000000000000000000000000000000000000 --- a/src/ui/layerbrowser/layerBrowser.module.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - GetInitialLayerOpacityPipe, - LayerBrowser, - LockedLayerBtnClsPipe -} from './layerBrowserComponent/layerbrowser.component' -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { AngularMaterialModule } from 'src/sharedModules'; -import { LayerDetailComponent } from './layerDetail/layerDetail.component'; -import { FormsModule } from '@angular/forms'; -import { UtilModule } from 'src/util'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - AngularMaterialModule, - UtilModule, - ], - declarations: [ - LayerBrowser, - LayerDetailComponent, - - GetInitialLayerOpacityPipe, - LockedLayerBtnClsPipe, - ], - exports: [ - GetInitialLayerOpacityPipe, - LayerBrowser, - LockedLayerBtnClsPipe - ] -}) - -export class LayerBrowserModule{} \ No newline at end of file diff --git a/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.component.ts b/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.component.ts deleted file mode 100644 index 14d933124d8e2921b72e2f34996adcbaf40a9520..0000000000000000000000000000000000000000 --- a/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.component.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, OnInit, Output, Pipe, PipeTransform } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { combineLatest, Observable, Subscription } from "rxjs"; -import { debounceTime, distinctUntilChanged, map, shareReplay, startWith } from "rxjs/operators"; -import { MatSliderChange } from "@angular/material/slider"; - -import { getViewer } from "src/util/fn"; -import { PureContantService } from "src/util"; -import { ngViewerActionRemoveNgLayer, ngViewerActionForceShowSegment } from "src/services/state/ngViewerState/actions"; -import { getNgIds } from 'src/util/fn' -import { LoggingService } from "src/logging"; -import { ARIA_LABELS } from 'common/constants' - -import { INgLayerInterface } from "../index"; - -const SHOW_LAYER_NAMES = [ - 'PLI Fiber Orientation Red Channel', - 'PLI Fiber Orientation Green Channel', - 'PLI Fiber Orientation Blue Channel', - 'Blockface Image', - 'PLI Transmittance', - 'T2w MRI', - 'MRI Labels', - 'VOI_1 (area V1)', - 'VOI_2 (area V2)' -] - -@Component({ - selector : 'layer-browser', - templateUrl : './layerbrowser.template.html', - styleUrls : [ - './layerbrowser.style.css', - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class LayerBrowser implements OnInit, OnDestroy { - - public TOGGLE_SHOW_LAYER_CONTROL_ARIA_LABEL = ARIA_LABELS.TOGGLE_SHOW_LAYER_CONTROL - - @Output() public nonBaseLayersChanged: EventEmitter<INgLayerInterface[]> = new EventEmitter() - - /** - * TODO make untangle nglayernames and its dependency on ng - */ - public loadedNgLayers$: Observable<INgLayerInterface[]> - public lockedLayers: string[] = [] - - public nonBaseNgLayers$: Observable<INgLayerInterface[]> - - public forceShowSegmentCurrentState: boolean | null = null - public forceShowSegment$: Observable<boolean|null> - - public ngLayers$: Observable<string[]> - public advancedMode: boolean = false - - private subscriptions: Subscription[] = [] - private disposeHandler: any - - @Input() - public showPlaceholder: boolean = true - - public darktheme$: Observable<boolean> - - private customNgLayers: string[] = ['spatial landmark layer'] - - constructor( - private store: Store<any>, - private pureConstantSvc: PureContantService, - private log: LoggingService, - ) { - this.ngLayers$ = store.pipe( - select('viewerState'), - select('templateSelected'), - map(templateSelected => { - if (!templateSelected) { return [] } - if (this.advancedMode) { return [] } - - const { ngId , otherNgIds = []} = templateSelected - - return [ - ngId, - ...this.customNgLayers, - ...otherNgIds, - ...templateSelected.parcellations.reduce((acc, curr) => { - return acc.concat([ - curr.ngId, - ...getNgIds(curr.regions), - ]) - }, []), - ] - }), - /** - * get unique array - */ - map(nonUniqueArray => Array.from(new Set(nonUniqueArray))), - /** - * remove falsy values - */ - map(arr => arr.filter(v => !!v)), - ) - - this.loadedNgLayers$ = this.store.pipe( - select('viewerState'), - select('loadedNgLayers'), - ) - - this.nonBaseNgLayers$ = combineLatest( - this.ngLayers$, - this.loadedNgLayers$, - ).pipe( - map(([baseNgLayerNames, loadedNgLayers]) => { - const baseNameSet = new Set(baseNgLayerNames) - return loadedNgLayers.filter(l => SHOW_LAYER_NAMES.includes(l.name)) - }), - distinctUntilChanged() - ) - - this.forceShowSegment$ = this.store.pipe( - select('ngViewerState'), - select('forceShowSegment'), - startWith(false) - ) - - this.darktheme$ = this.pureConstantSvc.darktheme$.pipe( - shareReplay(1), - ) - - } - - public ngOnInit() { - this.subscriptions.push( - this.nonBaseNgLayers$.pipe( - // on switching template, non base layer will fire - // debounce to ensure that the non base layer is indeed an extra layer - debounceTime(160), - ).subscribe(layers => this.nonBaseLayersChanged.emit(layers)), - ) - this.subscriptions.push( - this.forceShowSegment$.subscribe(state => this.forceShowSegmentCurrentState = state), - ) - - } - - public ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()) - } - - public classVisible(layer: any): boolean { - return typeof layer.visible === 'undefined' - ? true - : layer.visible - } - - public checkLocked(ngLayer: INgLayerInterface): boolean { - if (!this.lockedLayers) { - /* locked layer undefined. always return false */ - return false - } else { - return this.lockedLayers.findIndex(l => l === ngLayer.name) >= 0 - } - } - - get viewer() { - return getViewer() - } - - public toggleVisibility(layer: any) { - const layerName = layer.name - if (!layerName) { - this.log.error('layer name not defined', layer) - return - } - const ngLayer = this.viewer.layerManager.getLayerByName(layerName) - if (!ngLayer) { - this.log.error('ngLayer could not be found', layerName, this.viewer.layerManager.managedLayers) - } - ngLayer.setVisible(!ngLayer.visible) - } - - public toggleForceShowSegment(ngLayer: any) { - if (!ngLayer || ngLayer.type !== 'segmentation') { - /* toggle only on segmentation layer */ - return - } - - /** - * TODO perhaps useEffects ? - */ - this.store.dispatch( - ngViewerActionForceShowSegment({ - forceShowSegment : this.forceShowSegmentCurrentState === null - ? true - : this.forceShowSegmentCurrentState === true - ? false - : null, - }) - ) - } - - public removeLayer(layer: any) { - if (this.checkLocked(layer)) { - this.log.warn('this layer is locked and cannot be removed') - return - } - - this.store.dispatch( - ngViewerActionRemoveNgLayer({ - layer - }) - ) - } - - public changeOpacity(layerName: string, event: MatSliderChange){ - const { value } = event - const l = this.viewer.layerManager.getLayerByName(layerName) - if (!l) return - - if (typeof l.layer.opacity === 'object') { - l.layer.opacity.value = value - } else if (typeof l.layer.displayState === 'object') { - l.layer.displayState.selectedAlpha.value = value - } else { - this.log.warn({ - msg: `layer does not belong anywhere`, - layerName, - layer: l - }) - } - } - - /** - * TODO use observable and pipe to make this more perf - */ - public segmentationTooltip() { - return `toggle segments visibility: - ${this.forceShowSegmentCurrentState === true ? 'always show' : this.forceShowSegmentCurrentState === false ? 'always hide' : 'auto'}` - } - - get segmentationAdditionalClass() { - return this.forceShowSegmentCurrentState === null - ? 'blue' - : this.forceShowSegmentCurrentState === true - ? 'normal' - : this.forceShowSegmentCurrentState === false - ? 'muted' - : 'red' - } - - public matTooltipPosition: string = 'below' -} - -@Pipe({ - name: 'lockedLayerBtnClsPipe', -}) - -export class LockedLayerBtnClsPipe implements PipeTransform { - public transform(ngLayer: INgLayerInterface, lockedLayers?: string[]): boolean { - return (lockedLayers && new Set(lockedLayers).has(ngLayer.name)) || false - } -} - -@Pipe({ - name: 'getInitialLayerOpacityPipe' -}) - -export class GetInitialLayerOpacityPipe implements PipeTransform{ - public transform(viewer: any, layerName: string): number{ - if (!viewer) return 0 - const l = viewer.layerManager.getLayerByName(layerName) - if (!l || !l.layer) return 0 - if (typeof l.layer.opacity === 'object') return l.layer.opacity.value - else if (typeof l.layer.displayState === 'object') return l.layer.displayState.selectedAlpha.value - else return 0 - } -} diff --git a/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.style.css b/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.style.css deleted file mode 100644 index b38ff0ea019b33d7e8797092569c7cc865408ea2..0000000000000000000000000000000000000000 --- a/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.style.css +++ /dev/null @@ -1,11 +0,0 @@ -:host -{ - padding: 0 0.2em; - display: flex; - flex-direction: column-reverse; -} - -.noLayerPlaceHolder -{ - padding: 0.5em 1em; -} diff --git a/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.template.html b/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.template.html deleted file mode 100644 index 977a88beab32894a90f0f284452f1a29177b35ab..0000000000000000000000000000000000000000 --- a/src/ui/layerbrowser/layerBrowserComponent/layerbrowser.template.html +++ /dev/null @@ -1,112 +0,0 @@ -<!-- n.b. using mousedown for event trigger --> -<!-- Chrome & FF exhibit different behaviours when using click/mouseup as a event handler --> -<!-- in Chrome, it will complain that expression changed after change detection --> -<!-- in FF, the element changes, and focusout event is never fired properly --> - -<ng-container *ngIf="nonBaseNgLayers$ | async as nonBaseNgLayers; else noLayerPlaceHolder"> - <mat-accordion *ngIf="nonBaseNgLayers.length > 0; else noLayerPlaceHolder" - [multi]="true" - displayMode="flat"> - <mat-expansion-panel - [disabled]="true" - *ngFor="let ngLayer of nonBaseNgLayers" - class="layer-expansion-unit" - #expansionPanel> - <mat-expansion-panel-header> - <div class="align-items-center d-flex flex-nowrap pr-4 w-100"> - - <button mat-icon-button - [matTooltip]="ngLayer.name | getFilenamePipe"> - <i class="fas fa-info"></i> - </button> - - <!-- toggle opacity --> - <div matTooltip="opacity"> - - <mat-slider - [disabled]="!ngLayer.visible" - min="0" - max="1" - (input)="changeOpacity(ngLayer.name, $event)" - [value]="viewer | getInitialLayerOpacityPipe: ngLayer.name" - step="0.01"> - - </mat-slider> - </div> - - <!-- toggle visibility --> - - <button - [matTooltipPosition]="matTooltipPosition" - [matTooltip]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'base layer cannot be hidden' : 'toggle visibility'" - (mousedown)="toggleVisibility(ngLayer)" - mat-icon-button - [disabled]="ngLayer | lockedLayerBtnClsPipe : lockedLayers" - [color]="ngLayer.visible ? 'primary' : null"> - <i [ngClass]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'fas fa-lock muted' : ngLayer.visible ? 'far fa-eye' : 'far fa-eye-slash'"> - </i> - </button> - - <!-- advanced mode only: toggle force show segmentation --> - <button - *ngIf="advancedMode" - [matTooltipPosition]="matTooltipPosition" - [matTooltip]="ngLayer.type === 'segmentation' ? segmentationTooltip() : 'only segmentation layer can hide/show segments'" - (mousedown)="toggleForceShowSegment(ngLayer)" - mat-icon-button> - <i - class="fas" - [ngClass]="ngLayer.type === 'segmentation' ? ('fa-th-large ' + segmentationAdditionalClass) : 'fa-lock muted' "> - - </i> - </button> - - <!-- remove layer --> - <button - color="warn" - mat-icon-button - (mousedown)="removeLayer(ngLayer)" - [disabled]="ngLayer | lockedLayerBtnClsPipe : lockedLayers" - [matTooltip]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'base layers cannot be removed' : 'remove layer'"> - <i [class]="(ngLayer | lockedLayerBtnClsPipe : lockedLayers) ? 'fas fa-lock muted' : 'fas fa-trash'"> - </i> - </button> - - <!-- layer description --> - <mat-label - [matTooltipPosition]="matTooltipPosition" - [matTooltip]="ngLayer.name | getFilenamePipe" - [class]="((darktheme$ | async) ? 'text-light' : 'text-dark') + ' text-truncate flex-grow-1 flex-shrink-1'"> - {{ ngLayer.name | getFilenamePipe }} - </mat-label> - - <button mat-icon-button - [attr.aria-label]="TOGGLE_SHOW_LAYER_CONTROL_ARIA_LABEL" - (click)="expansionPanel.toggle()"> - <ng-container *ngIf="expansionPanel.expanded; else btnIconAlt"> - <i class="fas fa-chevron-up"></i> - </ng-container> - - <ng-template #btnIconAlt> - <i class="fas fa-chevron-down"></i> - </ng-template> - </button> - - </div> - </mat-expansion-panel-header> - - <ng-template matExpansionPanelContent> - <layer-detail-cmp [layerName]="ngLayer.name"> - </layer-detail-cmp> - </ng-template> - - </mat-expansion-panel> - </mat-accordion> -</ng-container> - -<!-- fall back when no layers are showing --> -<ng-template #noLayerPlaceHolder> - <small *ngIf="showPlaceholder" class="noLayerPlaceHolder text-muted"> - No additional layers added. - </small> -</ng-template> diff --git a/src/ui/layerbrowser/layerDetail/layerDetail.component.spec.ts b/src/ui/layerbrowser/layerDetail/layerDetail.component.spec.ts deleted file mode 100644 index 575f538b1b828342c9f290f37aab9bf2c2209556..0000000000000000000000000000000000000000 --- a/src/ui/layerbrowser/layerDetail/layerDetail.component.spec.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { LayerDetailComponent, VIEWER_INJECTION_TOKEN } from './layerDetail.component' -import { async, TestBed } from '@angular/core/testing' -import { NgLayersService } from '../ngLayerService.service' -import { By } from '@angular/platform-browser' -import * as CONSTANT from 'src/util/constants' -import { AngularMaterialModule } from 'src/sharedModules' -import { CommonModule } from '@angular/common' -import { FormsModule, ReactiveFormsModule } from '@angular/forms' - -const getSpies = (service: NgLayersService) => { - const lowThMapGetSpy = spyOn(service.lowThresholdMap, 'get').and.callThrough() - const highThMapGetSpy = spyOn(service.highThresholdMap, 'get').and.callThrough() - const brightnessMapGetSpy = spyOn(service.brightnessMap, 'get').and.callThrough() - const contractMapGetSpy = spyOn(service.contrastMap, 'get').and.callThrough() - const removeBgMapGetSpy = spyOn(service.removeBgMap, 'get').and.callThrough() - - const lowThMapSetSpy = spyOn(service.lowThresholdMap, 'set').and.callThrough() - const highThMapSetSpy = spyOn(service.highThresholdMap, 'set').and.callThrough() - const brightnessMapSetSpy = spyOn(service.brightnessMap, 'set').and.callThrough() - const contrastMapSetSpy = spyOn(service.contrastMap, 'set').and.callThrough() - const removeBgMapSetSpy = spyOn(service.removeBgMap, 'set').and.callThrough() - - return { - lowThMapGetSpy, - highThMapGetSpy, - brightnessMapGetSpy, - contractMapGetSpy, - removeBgMapGetSpy, - lowThMapSetSpy, - highThMapSetSpy, - brightnessMapSetSpy, - contrastMapSetSpy, - removeBgMapSetSpy, - } -} - -const getCtrl = () => { - const lowThSlider = By.css('mat-slider[aria-label="Set lower threshold"]') - const highThSlider = By.css('mat-slider[aria-label="Set higher threshold"]') - const brightnessSlider = By.css('mat-slider[aria-label="Set brightness"]') - const contrastSlider = By.css('mat-slider[aria-label="Set contrast"]') - const removeBgSlideToggle = By.css('mat-slide-toggle[aria-label="Remove background"]') - return { - lowThSlider, - highThSlider, - brightnessSlider, - contrastSlider, - removeBgSlideToggle, - } -} - -const getSliderChangeTest = ctrlName => describe(`testing: ${ctrlName}`, () => { - - it('on change, calls window', () => { - const service = TestBed.inject(NgLayersService) - const spies = getSpies(service) - - const fixture = TestBed.createComponent(LayerDetailComponent) - const layerName = `hello-kitty` - fixture.componentInstance.layerName = layerName - const triggerChSpy = spyOn(fixture.componentInstance, 'triggerChange') - const ctrls = getCtrl() - - const sLower = fixture.debugElement.query( ctrls[`${ctrlName}Slider`] ) - sLower.componentInstance.input.emit({ value: 0.5 }) - expect(spies[`${ctrlName}MapSetSpy`]).toHaveBeenCalledWith(layerName, 0.5) - expect(triggerChSpy).toHaveBeenCalled() - }) -}) - -const fragmentMainSpy = { - value: `test value`, - restoreState: () => {} -} - -const defaultViewer = { - layerManager: { - getLayerByName: jasmine.createSpy('getLayerByName').and.returnValue({layer: {fragmentMain: fragmentMainSpy}}) - } -} - -describe('> layerDetail.component.ts', () => { - describe('> LayerDetailComponent', () => { - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ - LayerDetailComponent - ], - imports: [ - AngularMaterialModule, - CommonModule, - FormsModule, - ReactiveFormsModule, - ], - providers: [ - NgLayersService, - { - provide: VIEWER_INJECTION_TOKEN, - useValue: defaultViewer - } - ] - }).compileComponents() - })) - - describe('> basic funcitonalities', () => { - - it('> it should be created', () => { - const fixture = TestBed.createComponent(LayerDetailComponent) - const element = fixture.debugElement.componentInstance - expect(element).toBeTruthy() - }) - - it('> on bind input, if input is truthy, calls get on layerService maps', () => { - TestBed.overrideProvider(VIEWER_INJECTION_TOKEN, { - useValue: {} - }) - const service = TestBed.inject(NgLayersService) - const { - brightnessMapGetSpy, - contractMapGetSpy, - highThMapGetSpy, - lowThMapGetSpy, - removeBgMapGetSpy - } = getSpies(service) - - const layerName = `hello-kitty` - const fixture = TestBed.createComponent(LayerDetailComponent) - fixture.componentInstance.layerName = layerName - fixture.componentInstance.ngOnChanges() - fixture.detectChanges() - expect(brightnessMapGetSpy).toHaveBeenCalledWith(layerName) - expect(contractMapGetSpy).toHaveBeenCalledWith(layerName) - expect(highThMapGetSpy).toHaveBeenCalledWith(layerName) - expect(lowThMapGetSpy).toHaveBeenCalledWith(layerName) - expect(removeBgMapGetSpy).toHaveBeenCalledWith(layerName) - }) - - it('> on bind input, if input is falsy, does not call layerService map get', () => { - const service = TestBed.inject(NgLayersService) - const { - brightnessMapGetSpy, - contractMapGetSpy, - highThMapGetSpy, - lowThMapGetSpy, - removeBgMapGetSpy - } = getSpies(service) - - const layerName = null - const fixture = TestBed.createComponent(LayerDetailComponent) - fixture.componentInstance.layerName = layerName - fixture.componentInstance.ngOnChanges() - fixture.detectChanges() - expect(brightnessMapGetSpy).not.toHaveBeenCalled() - expect(contractMapGetSpy).not.toHaveBeenCalled() - expect(highThMapGetSpy).not.toHaveBeenCalled() - expect(lowThMapGetSpy).not.toHaveBeenCalled() - expect(removeBgMapGetSpy).not.toHaveBeenCalled() - }) - - }) - - const testingSlidersCtrl = [ - 'lowTh', - 'highTh', - 'brightness', - 'contrast', - ] - - for (const sliderCtrl of testingSlidersCtrl ) { - getSliderChangeTest(sliderCtrl) - } - - describe('testing: removeBG toggle', () => { - it('on change, calls window', () => { - - const service = TestBed.inject(NgLayersService) - const { removeBgMapSetSpy } = getSpies(service) - - const fixture = TestBed.createComponent(LayerDetailComponent) - const triggerChSpy = spyOn(fixture.componentInstance, 'triggerChange') - const layerName = `hello-kitty` - fixture.componentInstance.layerName = layerName - - const { removeBgSlideToggle } = getCtrl() - const bgToggle = fixture.debugElement.query( removeBgSlideToggle ) - bgToggle.componentInstance.change.emit({ checked: true }) - expect(removeBgMapSetSpy).toHaveBeenCalledWith('hello-kitty', true) - expect(triggerChSpy).toHaveBeenCalled() - - removeBgMapSetSpy.calls.reset() - triggerChSpy.calls.reset() - expect(removeBgMapSetSpy).not.toHaveBeenCalled() - expect(triggerChSpy).not.toHaveBeenCalled() - - bgToggle.componentInstance.change.emit({ checked: false }) - - expect(removeBgMapSetSpy).toHaveBeenCalledWith('hello-kitty', false) - expect(triggerChSpy).toHaveBeenCalled() - }) - }) - - describe('triggerChange', () => { - it('should throw if viewer is not defined', () => { - TestBed.overrideProvider(VIEWER_INJECTION_TOKEN, { - useValue: null - }) - const fixutre = TestBed.createComponent(LayerDetailComponent) - expect(function(){ - fixutre.componentInstance.triggerChange() - }).toThrowError('viewer is not defined') - }) - - it('should throw if layer is not found', () => { - const fakeGetLayerByName = jasmine.createSpy().and.returnValue(undefined) - const fakeNgInstance = { - layerManager: { - getLayerByName: fakeGetLayerByName - } - } - - TestBed.overrideProvider(VIEWER_INJECTION_TOKEN, { - useValue: fakeNgInstance - }) - - const fixutre = TestBed.createComponent(LayerDetailComponent) - const layerName = `test-kitty` - - fixutre.componentInstance.layerName = layerName - - expect(function(){ - fixutre.componentInstance.triggerChange() - }).toThrowError(`layer with name: ${layerName}, not found.`) - }) - - it('should throw if layer.layer.fragmentMain is undefined', () => { - const layerName = `test-kitty` - - const fakeLayer = { - hello: 'world' - } - const fakeGetLayerByName = jasmine.createSpy().and.returnValue(fakeLayer) - const fakeNgInstance = { - layerManager: { - getLayerByName: fakeGetLayerByName - } - } - - TestBed.overrideProvider(VIEWER_INJECTION_TOKEN, { - useValue: fakeNgInstance - }) - - const fixutre = TestBed.createComponent(LayerDetailComponent) - - fixutre.componentInstance.layerName = layerName - - expect(function(){ - fixutre.componentInstance.triggerChange() - }).toThrowError(`layer.fragmentMain is not defined... is this an image layer?`) - }) - - it('should call getShader and restoreState if all goes right', () => { - - const replacementShader = `blabla ahder` - const getShaderSpy = jasmine.createSpy('getShader').and.returnValue(replacementShader) - spyOnProperty(CONSTANT, 'getShader').and.returnValue(getShaderSpy) - - const layerName = `test-kitty` - - const fakeRestoreState = jasmine.createSpy('fakeGetLayerByName') - const fakeLayer = { - layer: { - fragmentMain: { - restoreState: fakeRestoreState - } - } - } - const fakeGetLayerByName = jasmine.createSpy('fakeGetLayerByName').and.returnValue(fakeLayer) - const fakeNgInstance = { - layerManager: { - getLayerByName: fakeGetLayerByName - } - } - TestBed.overrideProvider(VIEWER_INJECTION_TOKEN, { - useValue: fakeNgInstance - }) - - const fixutre = TestBed.createComponent(LayerDetailComponent) - fixutre.componentInstance.layerName = layerName - fixutre.detectChanges() - - fixutre.componentInstance.triggerChange() - - expect(fakeGetLayerByName).toHaveBeenCalledWith(layerName) - expect(getShaderSpy).toHaveBeenCalled() - expect(fakeRestoreState).toHaveBeenCalledWith(replacementShader) - }) - }) - }) -}) \ No newline at end of file diff --git a/src/ui/layerbrowser/layerDetail/layerDetail.component.ts b/src/ui/layerbrowser/layerDetail/layerDetail.component.ts deleted file mode 100644 index 414bab57e2f2ef11e8232a7405e8b206d5707cda..0000000000000000000000000000000000000000 --- a/src/ui/layerbrowser/layerDetail/layerDetail.component.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Component, Input, OnChanges, ChangeDetectionStrategy, Optional, Inject } from "@angular/core"; -import { NgLayersService } from "../ngLayerService.service"; -import { MatSliderChange } from "@angular/material/slider"; -import { MatSlideToggleChange } from "@angular/material/slide-toggle"; -import { getShader } from "src/util/constants"; - -export const VIEWER_INJECTION_TOKEN = `VIEWER_INJECTION_TOKEN` - -@Component({ - selector: 'layer-detail-cmp', - templateUrl: './layerDetail.template.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class LayerDetailComponent implements OnChanges{ - @Input() - layerName: string - - private colormap = null - - constructor( - private layersService: NgLayersService, - @Optional() @Inject(VIEWER_INJECTION_TOKEN) private injectedViewer - ){ - - } - - ngOnChanges(){ - if (!this.layerName) return - - this.lowThreshold = this.layersService.lowThresholdMap.get(this.layerName) || this.lowThreshold - this.highThreshold = this.layersService.highThresholdMap.get(this.layerName) || this.highThreshold - this.brightness = this.layersService.brightnessMap.get(this.layerName) || this.brightness - this.contrast = this.layersService.contrastMap.get(this.layerName) || this.contrast - this.removeBg = this.layersService.removeBgMap.get(this.layerName) || this.removeBg - this.colormap = this.layersService.colorMapMap.get(this.layerName) || this.colormap - } - - public lowThreshold: number = 0 - public highThreshold: number = 1 - public brightness: number = 0 - public contrast: number = 0 - public removeBg: boolean = false - - handleChange(mode: 'low' | 'high' | 'brightness' | 'contrast', event: MatSliderChange){ - switch(mode) { - case 'low': - this.layersService.lowThresholdMap.set(this.layerName, event.value) - this.lowThreshold = event.value - break; - case 'high': - this.layersService.highThresholdMap.set(this.layerName, event.value) - this.highThreshold = event.value - break; - case 'brightness': - this.layersService.brightnessMap.set(this.layerName, event.value) - this.brightness = event.value - break; - case 'contrast': - this.layersService.contrastMap.set(this.layerName, event.value) - this.contrast = event.value - break; - default: return - } - this.triggerChange() - } - - handleToggleBg(event: MatSlideToggleChange){ - this.layersService.removeBgMap.set(this.layerName, event.checked) - this.removeBg = event.checked - this.triggerChange() - } - - triggerChange(){ - const { lowThreshold, highThreshold, brightness, contrast, removeBg, colormap } = this - const shader = getShader({ - lowThreshold, - highThreshold, - colormap, - brightness, - contrast, - removeBg - }) - this.fragmentMain.restoreState(shader) - } - - private get viewer(){ - return this.injectedViewer || (window as any).viewer - } - - private get fragmentMain(){ - - if (!this.viewer) throw new Error(`viewer is not defined`) - const layer = this.viewer.layerManager.getLayerByName(this.layerName) - if (!layer) throw new Error(`layer with name: ${this.layerName}, not found.`) - if (! (layer.layer?.fragmentMain?.restoreState) ) throw new Error(`layer.fragmentMain is not defined... is this an image layer?`) - - return layer.layer.fragmentMain - } -} diff --git a/src/ui/layerbrowser/layerDetail/layerDetail.template.html b/src/ui/layerbrowser/layerDetail/layerDetail.template.html deleted file mode 100644 index 376b875c3ffd924a5a69166d2555104cae0c0390..0000000000000000000000000000000000000000 --- a/src/ui/layerbrowser/layerDetail/layerDetail.template.html +++ /dev/null @@ -1,79 +0,0 @@ -<div class="d-flex flex-column"> - <div> - <mat-label> - Low threshold - </mat-label> - <mat-slider - aria-label="Set lower threshold" - (input)="handleChange('low', $event)" - [value]="lowThreshold" - min="0" - max="1" - step="0.001"> - </mat-slider> - <mat-label> - {{ lowThreshold }} - </mat-label> - </div> - - <div> - <mat-label> - High threshold - </mat-label> - <mat-slider - aria-label="Set higher threshold" - (input)="handleChange('high', $event)" - [value]="highThreshold" - min="0" - max="1" - step="0.001"> - </mat-slider> - <mat-label> - {{ highThreshold }} - </mat-label> - </div> - <div> - <mat-label> - Brightness - </mat-label> - <mat-slider - aria-label="Set brightness" - (input)="handleChange('brightness', $event)" - [value]="brightness" - min="-1" - max="1" - step="0.01"> - </mat-slider> - <mat-label> - {{ brightness }} - </mat-label> - </div> - <div> - <mat-label> - Contrast - </mat-label> - <mat-slider - aria-label="Set contrast" - (input)="handleChange('contrast', $event)" - [value]="contrast" - min="-1" - max="1" - step="0.01"> - </mat-slider> - <mat-label> - {{ contrast }} - </mat-label> - </div> - - <div> - <mat-label> - Remove background - </mat-label> - <mat-slide-toggle - aria-label="Remove background" - (change)="handleToggleBg($event)" - [(ngModel)]="removeBg"> - - </mat-slide-toggle> - </div> -</div> \ No newline at end of file diff --git a/src/ui/layerbrowser/ngLayerService.service.ts b/src/ui/layerbrowser/ngLayerService.service.ts deleted file mode 100644 index 35bd949ba6885423862692dacdac94e35dc96a7b..0000000000000000000000000000000000000000 --- a/src/ui/layerbrowser/ngLayerService.service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Injectable } from "@angular/core"; -import { EnumColorMapName } from "src/util/colorMaps"; - -@Injectable({ - providedIn: 'root' -}) - -export class NgLayersService{ - public lowThresholdMap: Map<string, number> = new Map() - public highThresholdMap: Map<string, number> = new Map() - public brightnessMap: Map<string, number> = new Map() - public contrastMap: Map<string, number> = new Map() - public removeBgMap: Map<string, boolean> = new Map() - public colorMapMap: Map<string, EnumColorMapName> = new Map() -} diff --git a/src/ui/logoContainer/logoContainer.component.ts b/src/ui/logoContainer/logoContainer.component.ts index 105e0726251a75522bc2d747767c0fe7b3eb50c2..3cc1e6d947db7c42e80c251d7c0de1e67bbc4339 100644 --- a/src/ui/logoContainer/logoContainer.component.ts +++ b/src/ui/logoContainer/logoContainer.component.ts @@ -1,7 +1,7 @@ -import { Component } from "@angular/core"; -import { PureContantService } from "src/util"; -import { Subscription } from "rxjs"; +import { Component, Inject } from "@angular/core"; +import { Observable, Subscription } from "rxjs"; import { distinctUntilChanged } from "rxjs/operators"; +import { DARKTHEME } from "src/util/injectionTokens"; const imageDark = 'assets/logo/ebrains-logo-dark.svg' const imageLight = 'assets/logo/ebrains-logo-light.svg' @@ -24,10 +24,10 @@ export class LogoContainer { private subscriptions: Subscription[] = [] constructor( - private pureConstantService: PureContantService + @Inject(DARKTHEME) darktheme$: Observable<boolean> ){ this.subscriptions.push( - pureConstantService.darktheme$.pipe( + darktheme$.pipe( distinctUntilChanged() ).subscribe(flag => { this.containerStyle = { diff --git a/src/ui/nehubaContainer/2dLandmarks/flatLm/flatLm.component.ts b/src/ui/nehubaContainer/2dLandmarks/flatLm/flatLm.component.ts deleted file mode 100644 index e425aca205c81b4e644508a769f280e65c6abcc0..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/2dLandmarks/flatLm/flatLm.component.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, OnChanges, SimpleChanges } from "@angular/core"; -import { LandmarkUnitBase } from "../landmark.base"; - -@Component({ - selector: 'landmark-2d-flat-cmp', - templateUrl: './flatLm.template.html', - styleUrls: [ - './flatLm.style.css' - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) - -export class FlatLMCmp extends LandmarkUnitBase implements OnChanges{ - - @HostBinding('style.transform') - transform = `translate(0px, 0px)` - - @HostBinding('style.opacity') - opacity = 1.0 - - @HostBinding('style.text-shadow') - textShadow = null - - private scale: number = 1 - - constructor(private cdr: ChangeDetectorRef){ - super() - this.cdr.detach() - } - - ngOnChanges(){ - - const zModifier = Math.tanh(this.positionZ/10) - if (this.positionZ >= 0) { - const shadowLength = zModifier * 4 - this.textShadow = `0 ${shadowLength}px ${shadowLength}px black` - this.opacity = 1.0 - } else { - this.textShadow = null - /** - * assert(zModifier < 0) - */ - this.opacity = 1.0 + (zModifier * 0.8) - } - - this.transform = `translate(${this.positionX}px, ${this.positionY - (zModifier >= 0 ? zModifier * 4 : 0) }px)` - } -} - -export const NORMAL_COLOR: number[] = [201,54,38] diff --git a/src/ui/nehubaContainer/2dLandmarks/flatLm/flatLm.style.css b/src/ui/nehubaContainer/2dLandmarks/flatLm/flatLm.style.css deleted file mode 100644 index 462b4c7efcef86358db18579f078dc9043c37fab..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/2dLandmarks/flatLm/flatLm.style.css +++ /dev/null @@ -1,10 +0,0 @@ -:host -{ - position: absolute; - color: rgba(201, 54, 38, 1.0) -} - -:host > div -{ - transform: translate(-50%, -50%); -} diff --git a/src/ui/nehubaContainer/2dLandmarks/flatLm/flatLm.template.html b/src/ui/nehubaContainer/2dLandmarks/flatLm/flatLm.template.html deleted file mode 100644 index a28b3899ac23b46084447e6104158475ca30f932..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/2dLandmarks/flatLm/flatLm.template.html +++ /dev/null @@ -1,3 +0,0 @@ -<div class="pe-all"> - <i class="fas fa-circle"></i> -</div> diff --git a/src/ui/nehubaContainer/2dLandmarks/landmark.base.ts b/src/ui/nehubaContainer/2dLandmarks/landmark.base.ts deleted file mode 100644 index 953c75ed49d956b42023822fec53daea65adcec7..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/2dLandmarks/landmark.base.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Directive, Input } from "@angular/core" - -@Directive() -export class LandmarkUnitBase{ - @Input() public positionX: number = 0 - @Input() public positionY: number = 0 - @Input() public positionZ: number = 0 - - @Input() public color: number[] = [255, 255, 255] -} diff --git a/src/ui/nehubaContainer/2dLandmarks/module.ts b/src/ui/nehubaContainer/2dLandmarks/module.ts deleted file mode 100644 index cef4ec49316a4723d47a2ebce00ebe43dad6020b..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/2dLandmarks/module.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { FlatLMCmp } from "./flatLm/flatLm.component"; -import { SafeStylePipe } from "./safeStyle.pipe"; - -@NgModule({ - imports: [ - CommonModule, - ], - declarations: [ - FlatLMCmp, - - /** - * pipes - */ - SafeStylePipe, - ], - exports: [ - FlatLMCmp, - ] -}) - -export class Landmark2DModule{} diff --git a/src/ui/nehubaContainer/2dLandmarks/safeStyle.pipe.ts b/src/ui/nehubaContainer/2dLandmarks/safeStyle.pipe.ts deleted file mode 100644 index 7901ca1cc515310cb40f495d9ffe7f520f81a5e9..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/2dLandmarks/safeStyle.pipe.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { DomSanitizer, SafeStyle } from "@angular/platform-browser"; - -@Pipe({ - name : 'safeStyle', -}) - -export class SafeStylePipe implements PipeTransform { - constructor(private sanitizer: DomSanitizer) { - - } - - public transform(style: string): SafeStyle { - return this.sanitizer.bypassSecurityTrustStyle(style) - } -} diff --git a/src/ui/nehubaContainer/pipes/mobileControlNubStyle.pipe.ts b/src/ui/nehubaContainer/pipes/mobileControlNubStyle.pipe.ts deleted file mode 100644 index 1f70f6b658cb27001bab3415c77aa0ec90d5be70..0000000000000000000000000000000000000000 --- a/src/ui/nehubaContainer/pipes/mobileControlNubStyle.pipe.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Pipe, PipeTransform } from "@angular/core"; -import { PANELS } from 'src/services/state/ngViewerState.store.helper' - - -@Pipe({ - name: 'mobileControlNubStylePipe', -}) - -export class MobileControlNubStylePipe implements PipeTransform { - public transform(panelMode: string): any { - switch (panelMode) { - case PANELS.SINGLE_PANEL: - return { - top: '80%', - left: '95%', - } - case PANELS.V_ONE_THREE: - case PANELS.H_ONE_THREE: - return { - top: '66.66%', - left: '66.66%', - } - case PANELS.FOUR_PANEL: - default: - return { - top: '50%', - left: '50%', - } - } - } -} diff --git a/src/ui/quickTour/module.ts b/src/ui/quickTour/module.ts index b599aeca491397515b1e271453878144e0a4f187..4f303b92d556132acf2a46afd452a9269a424779 100644 --- a/src/ui/quickTour/module.ts +++ b/src/ui/quickTour/module.ts @@ -21,7 +21,7 @@ import {StartTourDialogDialog} from "src/ui/quickTour/startTourDialog/startTourD WindowResizeModule, ComponentsModule, ], - declarations:[ + declarations: [ QuickTourThis, QuickTourComponent, QuickTourDirective, @@ -32,7 +32,7 @@ import {StartTourDialogDialog} from "src/ui/quickTour/startTourDialog/startTourD QuickTourDirective, QuickTourThis, ], - providers:[ + providers: [ { provide: OverlayContainer, useClass: FullscreenOverlayContainer @@ -42,7 +42,6 @@ import {StartTourDialogDialog} from "src/ui/quickTour/startTourDialog/startTourD provide: QUICK_TOUR_CMP_INJTKN, useValue: QuickTourComponent } - ], - entryComponents: [ StartTourDialogDialog ] + ] }) export class QuickTourModule{} diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index c33043639f957bbe8640db5e3c5104d682c14958..a657e32cc68916a0b63a485f226dbdd7a05e948e 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -1,39 +1,26 @@ import { NgModule } from "@angular/core"; import { ComponentsModule } from "src/components/components.module"; - import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { LayoutModule } from "src/layouts/layout.module"; - import { ScrollingModule } from "@angular/cdk/scrolling" import { HttpClientModule } from "@angular/common/http"; import { AngularMaterialModule } from 'src/sharedModules' import { UtilModule } from "src/util"; import { DownloadDirective } from "../util/directives/download.directive"; - -import { LogoContainer } from "./logoContainer/logoContainer.component"; import { MobileOverlay } from "./nehubaContainer/mobileOverlay/mobileOverlay.component"; -import { MobileControlNubStylePipe } from "./nehubaContainer/pipes/mobileControlNubStyle.pipe"; - import { HumanReadableFileSizePipe } from "src/util/pipes/humanReadableFileSize.pipe"; - import { ReorderPanelIndexPipe } from "./nehubaContainer/reorderPanelIndex.pipe"; - import { FixedMouseContextualContainerDirective } from "src/util/directives/FixedMouseContextualContainerDirective.directive"; - import { ShareModule } from "src/share"; import { AuthModule } from "src/auth"; import { ActionDialog } from "./actionDialog/actionDialog.component"; import { APPEND_SCRIPT_TOKEN, appendScriptFactory } from "src/util/constants"; import { DOCUMENT } from "@angular/common"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; -import { RegionalFeaturesModule } from "../atlasComponents/regionalFeatures"; -import { Landmark2DModule } from "./nehubaContainer/2dLandmarks/module"; import { HANDLE_SCREENSHOT_PROMISE, TypeHandleScrnShotPromise } from "../screenshot"; -import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; -import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; @NgModule({ - imports : [ + imports: [ BrowserAnimationsModule, HttpClientModule, FormsModule, @@ -45,24 +32,13 @@ import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; AngularMaterialModule, ShareModule, AuthModule, - RegionalFeaturesModule, - Landmark2DModule, - ParcellationRegionModule, - AtlasCmpParcellationModule, ], - declarations : [ - - LogoContainer, + declarations: [ MobileOverlay, - ActionDialog, - /* pipes */ - MobileControlNubStylePipe, - HumanReadableFileSizePipe, ReorderPanelIndexPipe, - /* directive */ DownloadDirective, FixedMouseContextualContainerDirective, @@ -71,7 +47,7 @@ import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; { provide: APPEND_SCRIPT_TOKEN, useFactory: appendScriptFactory, - deps: [ DOCUMENT ] + deps: [DOCUMENT] }, { provide: HANDLE_SCREENSHOT_PROMISE, @@ -98,7 +74,6 @@ import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; const context = subCanvas.getContext('2d') context.drawImage( canvas, - /** * from */ @@ -106,7 +81,6 @@ import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; y, width, height, - /** * to */ @@ -115,7 +89,6 @@ import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; width, height ) - subCanvas.toBlob(blob => { const url = URL.createObjectURL(blob) rs({ @@ -127,18 +100,9 @@ import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; }) as TypeHandleScrnShotPromise } ], - entryComponents : [ - - /* dynamically created components needs to be declared here */ - - ActionDialog, - ], - exports : [ + exports: [ // NehubaContainer, - - LogoContainer, MobileOverlay, - // StatusCardComponent, FixedMouseContextualContainerDirective, ] diff --git a/src/util/array.ts b/src/util/array.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c9cfeea640639773c0249db3f64406a6a67dac4 --- /dev/null +++ b/src/util/array.ts @@ -0,0 +1,13 @@ +const defaultCmFn = <T>(o: T, n: T) => o === n +export function arrayEqual<T>(compareFn: (o1: T, o2: T) => boolean = defaultCmFn, order = false) { + return function(array1: T[], array2: T[]){ + if (order) { + for (const idx in array1) { + if (!compareFn(array1[idx], array2[idx])) return false + } + return true + } + return !!array1.every(it1 => array2.find(it2 => compareFn(it1, it2))) + && !!array2.every(it2 => array1.find(it1 => compareFn(it1, it2))) + } +} diff --git a/src/util/constants.ts b/src/util/constants.ts index 84ee596993c519223fc8f28487af1a0cb8372f4d..5c4e7185743a1dca35e0523f076385b797fc21b5 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -3,8 +3,7 @@ import { environment } from 'src/environments/environment' export const LOCAL_STORAGE_CONST = { GPU_LIMIT: 'fzj.xg.iv.GPU_LIMIT', - ANIMATION: 'fzj.xg.iv.ANIMATION_FLAG', - SAVED_REGION_SELECTIONS: 'fzj.xg.iv.SAVED_REGION_SELECTIONS', + ANIMATION: 'fzj.xg.iv.DISABLE_ANIMATION_FLAG', MOBILE_UI: 'fzj.xg.iv.MOBILE_UI', AGREE_COOKIE: 'fzj.xg.iv.AGREE_COOKIE', AGREE_KG_TOS: 'fzj.xg.iv.AGREE_KG_TOS', @@ -15,7 +14,6 @@ export const LOCAL_STORAGE_CONST = { export const COOKIE_VERSION = '0.3.0' export const KG_TOS_VERSION = '0.3.0' -export const DS_PREVIEW_URL = environment.DATASET_PREVIEW_URL export const BACKENDURL = (() => { const { BACKEND_URL } = environment if (!BACKEND_URL) return `` @@ -84,7 +82,6 @@ export const getShader = ({ removeBg = false } = {}): string => { const { header, main, premain } = mapKeyColorMap.get(colormap) || (() => { - console.warn(`colormap ${colormap} not found. Using default colormap instead`) return mapKeyColorMap.get(EnumColorMapName.GREYSCALE) })() diff --git a/src/util/directives/floatingContainer.directive.ts b/src/util/directives/floatingContainer.directive.ts deleted file mode 100644 index 4ff9eb3b102668138f6a0ab8ed3eeacf02296fbf..0000000000000000000000000000000000000000 --- a/src/util/directives/floatingContainer.directive.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Directive, ViewContainerRef } from "@angular/core"; -import { WidgetServices } from "src/widget"; - -@Directive({ - selector: '[floatingContainerDirective]', -}) - -export class FloatingContainerDirective { - constructor( - widgetService: WidgetServices, - viewContainerRef: ViewContainerRef, - ) { - widgetService.floatingContainer = viewContainerRef - } -} diff --git a/src/util/fn.spec.ts b/src/util/fn.spec.ts index 61bb2fd8cb23665bc85aacb76b298f026d2f89aa..efd5e418d29f6d46df2442bb8d02cb1a63b14840 100644 --- a/src/util/fn.spec.ts +++ b/src/util/fn.spec.ts @@ -3,78 +3,10 @@ import {} from 'jasmine' import { hot } from 'jasmine-marbles' import { Observable, of } from 'rxjs' import { switchMap } from 'rxjs/operators' -import { isSame, getGetRegionFromLabelIndexId, switchMapWaitFor, bufferUntil } from './fn' +import { switchMapWaitFor, bufferUntil } from './fn' describe(`> util/fn.ts`, () => { - describe('> #getGetRegionFromLabelIndexId', () => { - - const COLIN_JULICHBRAIN_LAYER_NAME = `COLIN_V25_LEFT_NG_SPLIT_HEMISPHERE` - const LABEL_INDEX = 12 - const dummyParc = { - regions: [ - { - name: 'foo-bar', - children: [ - { - name: 'foo-bar-region-ba', - ngId: `${COLIN_JULICHBRAIN_LAYER_NAME}-ba`, - labelIndex: LABEL_INDEX - }, - { - name: 'foo-bar-region+1', - ngId: COLIN_JULICHBRAIN_LAYER_NAME, - labelIndex: LABEL_INDEX + 1 - }, - { - name: 'foo-bar-region', - ngId: COLIN_JULICHBRAIN_LAYER_NAME, - labelIndex: LABEL_INDEX - } - ] - } - ] - } - it('translateds hoc1 from labelIndex to region', () => { - - const getRegionFromlabelIndexId = getGetRegionFromLabelIndexId({ - parcellation: { - ...dummyParc, - updated: true, - }, - }) - const fetchedRegion = getRegionFromlabelIndexId({ labelIndexId: `${COLIN_JULICHBRAIN_LAYER_NAME}#${LABEL_INDEX}` }) - expect(fetchedRegion).toBeTruthy() - expect(fetchedRegion.name).toEqual('foo-bar-region') - - }) - }) - describe(`> #isSame`, () => { - it('should return true with null, null', () => { - expect(isSame(null, null)).toBe(true) - }) - - it('should return true with string', () => { - expect(isSame('test', 'test')).toBe(true) - }) - - it(`should return true with numbers`, () => { - expect(isSame(12, 12)).toBe(true) - }) - - it('should return true with obj with name attribute', () => { - - const obj = { - name: 'hello', - } - const obj2 = { - name: 'hello', - world: 'world', - } - expect(isSame(obj, obj2)).toBe(true) - expect(obj).not.toEqual(obj2) - }) - }) describe('> #switchMapWaitFor', () => { const val = 'hello world' diff --git a/src/util/fn.ts b/src/util/fn.ts index 479958c477576d2e34b7cc8f1be6243048024992..e5ef8ac6bde4b72b67c2fb76f7b94edde8123476 100644 --- a/src/util/fn.ts +++ b/src/util/fn.ts @@ -1,12 +1,6 @@ -import { deserialiseParcRegionId } from 'common/util' import { interval, Observable, of } from 'rxjs' import { filter, mapTo, take } from 'rxjs/operators' -export function isSame(o, n) { - if (!o) { return !n } - return o === n || (o && n && o.name === n.name) -} - export function getViewer() { return (window as any).viewer } @@ -45,26 +39,10 @@ const recursiveFlatten = (region, {ngId}) => { ) } -export function recursiveFindRegionWithLabelIndexId({ regions, labelIndexId, inheritedNgId = 'root' }: {regions: any[], labelIndexId: string, inheritedNgId: string}) { - const { ngId, labelIndex } = deserialiseParcRegionId( labelIndexId ) - const fr1 = regions.map(r => recursiveFlatten(r, { ngId: inheritedNgId })) - const fr2 = fr1.reduce((acc, curr) => acc.concat(...curr), []) - const found = fr2.find(r => r.ngId === ngId && Number(r.labelIndex) === Number(labelIndex)) - if (found) { return found } - return null -} - export function getUuid(){ return crypto.getRandomValues(new Uint32Array(1))[0].toString(16) } -export const getGetRegionFromLabelIndexId = ({ parcellation }) => { - const { ngId: defaultNgId, regions } = parcellation - // if (!updated) throw new Error(`parcellation not yet updated`) - return ({ labelIndexId }) => - recursiveFindRegionWithLabelIndexId({ regions, labelIndexId, inheritedNgId: defaultNgId }) -} - type TPrimitive = string | number const include = <T extends TPrimitive>(el: T, arr: T[]) => arr.indexOf(el) >= 0 @@ -131,8 +109,7 @@ export class QuickHash { if (opts?.length) this.length = opts.length } - @CachedFunction() - getHash(str: string){ + static GetHash(str: string) { let hash = 0 for (const char of str) { const charCode = char.charCodeAt(0) @@ -141,6 +118,11 @@ export class QuickHash { } return hash.toString(16).slice(1) } + + @CachedFunction() + getHash(str: string){ + return QuickHash.GetHash(str) + } } // fsaverage uses threesurfer, which, whilst do not use ngId, uses 'left' and 'right' as keys diff --git a/src/util/generator.ts b/src/util/generator.ts index 9c9d14a9064fb6d113d7c648ae59c69575314c69..fcf39012ca428114ebe93974a5aa1951db8bef15 100644 --- a/src/util/generator.ts +++ b/src/util/generator.ts @@ -1,7 +1,7 @@ export function* timedValues(ms: number = 500, mode: string = 'linear') { const startTime = Date.now() - const getValue = (fraction) => { + const getValue = (fraction: number) => { switch (mode) { case 'linear': default: diff --git a/src/util/index.ts b/src/util/index.ts index 21b7d4d11094b30d4e750237e94ebc32a516cdce..37bf9c0b4ee949075bad7c12aaa2c66d9eaff0e2 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,5 +1,4 @@ export { UtilModule } from './util.module' -export { PureContantService } from './pureConstant.service' export { CLICK_INTERCEPTOR_INJECTOR, ClickInterceptor, CONTEXT_MENU_ITEM_INJECTOR, TClickInterceptorConfig, TContextMenu } from './injectionTokens' export { DoublyLinkedList, diff --git a/src/util/injectionTokens.ts b/src/util/injectionTokens.ts index f8720f5d6ce4ca2e19c473799afb695bee18be05..feb547176fb2e37b7fe5222e716d1cf8c257a5ce 100644 --- a/src/util/injectionTokens.ts +++ b/src/util/injectionTokens.ts @@ -1,4 +1,5 @@ import { InjectionToken } from "@angular/core"; +import { Observable } from "rxjs"; export const CLICK_INTERCEPTOR_INJECTOR = new InjectionToken<ClickInterceptor>('CLICK_INTERCEPTOR_INJECTOR') @@ -17,3 +18,5 @@ export type TContextMenu<T> = { register: (fn: T) => void deregister: (fn: (fn: T) => void) => void } + +export const DARKTHEME = new InjectionToken<Observable<boolean>>('DARKTHEME') diff --git a/src/util/interfaces.ts b/src/util/interfaces.ts index e5c2fbd9686a3f1036a5e34959c2e697e9f8d1a4..6c057cb1ebe6acb7381f58864a0511cb3c1dc061 100644 --- a/src/util/interfaces.ts +++ b/src/util/interfaces.ts @@ -16,13 +16,15 @@ export interface IHasFullId{ export type TOverwriteShowDatasetDialog = (arg: any) => void -export const OVERWRITE_SHOW_DATASET_DIALOG_TOKEN = new InjectionToken<TOverwriteShowDatasetDialog>('OVERWRITE_SHOW_DATASET_DIALOG_TOKEN') - export type TRegionOfInterest = { ['fullId']: string } -export const REGION_OF_INTEREST = new InjectionToken<Observable<TRegionOfInterest>>('RegionOfInterest') export const CANCELLABLE_DIALOG = new InjectionToken('CANCELLABLE_DIALOG') +export type CANCELLABLE_DIALOG_OPTS = Partial<{ + userCancelCallback: () => void + ariaLabel: string +}> + export type TTemplateImage = { name: string '@id': string diff --git a/src/util/json.ts b/src/util/json.ts new file mode 100644 index 0000000000000000000000000000000000000000..dc6125ad3d4c6de031608d32213ffda341a4fecd --- /dev/null +++ b/src/util/json.ts @@ -0,0 +1,19 @@ +import { arrayEqual } from "./array" + +const defaultCmFn = <T>(o: T, n: T) => o === n +export function jsonEqual<T>(compareFn: (o1: T, o2: T) => boolean = defaultCmFn) { + + return function(obj1: Record<string, T>, obj2: Record<string, T>){ + + /** + * first check if keys equal + */ + if (!arrayEqual()(Object.keys(obj1), Object.keys(obj2))) { + return false + } + for (const key in obj1) { + if (!compareFn(obj1[key], obj2[key])) return false + } + return true + } +} diff --git a/src/util/pipes/nmToMm.pipe.ts b/src/util/pipes/nmToMm.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..f156d6c88dc8fbfc45fb8ad7ce01fdf23a404d69 --- /dev/null +++ b/src/util/pipes/nmToMm.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from "@angular/core"; + +@Pipe({ + name: 'nmToMm', + pure: true +}) + +export class NmToMm implements PipeTransform{ + public transform(nums: number[]): number[] { + return nums.map(num => (num / 1e6)) + } +} diff --git a/src/util/priority.ts b/src/util/priority.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab703f76b14e1a299fa2ae017d81038a43c1c858 --- /dev/null +++ b/src/util/priority.ts @@ -0,0 +1,176 @@ +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from "@angular/common/http" +import { Injectable } from "@angular/core" +import { interval, merge, Observable, of, Subject, timer } from "rxjs" +import { catchError, filter, finalize, map, switchMapTo, take, takeWhile } from "rxjs/operators" + +export const PRIORITY_HEADER = 'x-sxplr-http-priority' + +type ResultBase = { + urlWithParams: string +} + +type Result<T> = { + result: HttpResponse<T> +} & ResultBase + +type ErrorResult = { + error: Error +} & ResultBase + +type Queue = { + urlWithParams: string + priority: number + req: HttpRequest<unknown> + next: HttpHandler +} + +@Injectable({ + providedIn: 'root' +}) +export class PriorityHttpInterceptor implements HttpInterceptor{ + + private retry = 5 + private disablePriority = false + + private priorityQueue: Queue[] = [] + + private currentJob: Set<string> = new Set() + private archive: Map<string, HttpResponse<unknown>> = new Map() + private queue$: Subject<Queue> = new Subject() + private result$: Subject<Result<unknown>> = new Subject() + private error$: Subject<ErrorResult> = new Subject() + + private forceCheck$ = new Subject() + + private counter = 0 + private max = 6 + + constructor(){ + this.forceCheck$.pipe( + switchMapTo( + merge( + timer(0), + interval(16) + ).pipe( + filter(() => this.counter < this.max), + takeWhile(() => this.priorityQueue.length > 0) + ) + ) + ).subscribe(() => { + const job = this.priorityQueue.pop() + if (!job) return + this.currentJob.add(job.urlWithParams) + this.queue$.next(job) + }) + + this.queue$.subscribe(({ next, req, urlWithParams }) => { + this.counter ++ + let retry = this.retry + next.handle(req).pipe( + finalize(() => { + this.counter -- + }), + catchError((err, obs) => { + if (retry >= 0) { + retry -- + return obs + } + return of(new Error(err)) + }), + ).subscribe(val => { + if (val instanceof Error) { + this.error$.next({ + urlWithParams, + error: val + }) + } + if (val instanceof HttpResponse) { + this.archive.set(urlWithParams, val) + this.result$.next({ + urlWithParams, + result: val + }) + } + }) + }) + } + + updatePriority(urlWithParams: string, newPriority: number) { + + if (this.currentJob.has(urlWithParams)) return + + const foundIdx = this.priorityQueue.findIndex(v => v.urlWithParams === urlWithParams) + if (foundIdx < 0) return false + const [ item ] = this.priorityQueue.splice(foundIdx, 1) + item.priority = newPriority + + this.insert(item) + this.forceCheck$.next(true) + return true + } + + private insert(obj: Queue) { + const { priority, urlWithParams, req } = obj + + if (this.archive.has(urlWithParams)) return + if (this.currentJob.has(urlWithParams)) return + + obj.req = req.clone({ + headers: req.headers.delete(PRIORITY_HEADER) + }) + + const existing = this.priorityQueue.find(q => q.urlWithParams === urlWithParams) + if (existing) { + if (existing.priority < priority) { + this.updatePriority(urlWithParams, priority) + } + return + } + const foundIdx = this.priorityQueue.findIndex(q => q.priority <= priority) + const useIndex = foundIdx >= 0 ? foundIdx : this.priorityQueue.length + this.priorityQueue.splice(useIndex, 0, obj) + } + + intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { + /** + * Do not use priority for requests other than get. + * Since the way in which serialization occurs is via path and query param... + * body is not used. + */ + if (this.disablePriority || req.method !== 'GET') { + return next.handle(req) + } + + const { urlWithParams } = req + if (this.archive.has(urlWithParams)) { + return of( + this.archive.get(urlWithParams).clone() + ) + } + + const priority = Number(req.headers.get(PRIORITY_HEADER) || 0) + const objToInsert: Queue = { + priority, + req, + next, + urlWithParams + } + + this.insert(objToInsert) + this.forceCheck$.next(true) + + return merge( + this.result$, + this.error$, + ).pipe( + filter(v => v.urlWithParams === urlWithParams), + take(1), + map(v => { + if (v instanceof Error) { + throw v + } + return (v as Result<unknown>).result + }) + ) + } +} diff --git a/src/util/pureConstant.service.spec.ts b/src/util/pureConstant.service.spec.ts deleted file mode 100644 index fa9ccf741bd443baf767d929d5786a08ecd190b7..0000000000000000000000000000000000000000 --- a/src/util/pureConstant.service.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing" -import { TestBed } from "@angular/core/testing" -import { MatSnackBarModule } from "@angular/material/snack-bar" -import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { BS_ENDPOINT } from "src/atlasComponents/regionalFeatures/bsFeatures" -import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service" -import { viewerStateFetchedAtlasesSelector, viewerStateFetchedTemplatesSelector } from "src/services/state/viewerState/selectors" -import { PureContantService, SIIBRA_API_VERSION_HEADER_KEY, SIIBRA_API_VERSION } from "./pureConstant.service" -import { TAtlas } from "./siibraApiConstants/types" - -const MOCK_BS_ENDPOINT = `http://localhost:1234` - -describe('> pureConstant.service.ts', () => { - describe('> PureContantService', () => { - let httpController: HttpTestingController - beforeEach(() => { - TestBed.configureTestingModule({ - imports:[ - HttpClientTestingModule, - MatSnackBarModule, - ], - providers: [ - provideMockStore(), - { - provide: AtlasWorkerService, - useValue: { - worker: null - } - }, - { - provide: BS_ENDPOINT, - useValue: MOCK_BS_ENDPOINT - } - ] - }) - - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateFetchedTemplatesSelector, []) - mockStore.overrideSelector(viewerStateFetchedAtlasesSelector, []) - httpController = TestBed.inject(HttpTestingController) - }) - - afterEach(() => { - httpController.verify() - }) - - it('> can be init', () => { - const service = TestBed.inject(PureContantService) - const exp = httpController.expectOne(`${MOCK_BS_ENDPOINT}/atlases`) - exp.flush([]) - expect(service).toBeTruthy() - }) - describe('> allFetchingReady$', () => { - const mockAtlas: TAtlas = { - id: 'mockatlas id', - name: 'mockatlas name', - links: { - parcellations: { - href: `${MOCK_BS_ENDPOINT}/mockatlas-parcellation-href` - }, - spaces: { - href: `${MOCK_BS_ENDPOINT}/atlas-spaces` - } - } - } - it('> can be init, and configuration emits allFetchingReady$', () => { - const service = TestBed.inject(PureContantService) - const exp = httpController.expectOne(`${MOCK_BS_ENDPOINT}/atlases`) - exp.flush([mockAtlas], { - headers: { - [SIIBRA_API_VERSION_HEADER_KEY]: SIIBRA_API_VERSION - } - }) - service.allFetchingReady$.subscribe() - - const expT1 = httpController.expectOne(`${MOCK_BS_ENDPOINT}/atlases/${encodeURIComponent(mockAtlas.id)}/spaces`) - expT1.flush([]) - - const expP1 = httpController.expectOne(`${MOCK_BS_ENDPOINT}/atlases/${encodeURIComponent(mockAtlas.id)}/parcellations`) - expP1.flush([]) - }) - - }) - }) -}) diff --git a/src/util/pureConstant.service.ts b/src/util/pureConstant.service.ts deleted file mode 100644 index c5f2aa4644e33a8a19d31fa0ef3e814acd625740..0000000000000000000000000000000000000000 --- a/src/util/pureConstant.service.ts +++ /dev/null @@ -1,853 +0,0 @@ -import { Inject, Injectable, OnDestroy } from "@angular/core"; -import { Store, select } from "@ngrx/store"; -import { Observable, Subscription, of, forkJoin, combineLatest, from } from "rxjs"; -import { viewerConfigSelectorUseMobileUi } from "src/services/state/viewerConfig.store.helper"; -import { shareReplay, tap, scan, catchError, filter, switchMap, map, distinctUntilChanged, mapTo } from "rxjs/operators"; -import { HttpClient } from "@angular/common/http"; -import { viewerStateFetchedTemplatesSelector, viewerStateSetFetchedAtlases } from "src/services/state/viewerState.store.helper"; -import { LoggingService } from "src/logging"; -import { viewerStateFetchedAtlasesSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors"; -import { BS_ENDPOINT, BACKENDURL } from "src/util/constants"; -import { flattenReducer } from 'common/util' -import { IVolumeTypeDetail, TAtlas, TId, TParc, TRegionDetail, TRegionSummary, TSpaceFull, TSpaceSummary, TVolumeSrc } from "./siibraApiConstants/types"; -import { MultiDimMap, recursiveMutate, mutateDeepMerge } from "./fn"; -import { patchRegions } from './patchPureConstants' -import { environment } from "src/environments/environment"; -import { MatSnackBar } from "@angular/material/snack-bar"; -import { TTemplateImage } from "./interfaces"; - -export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version' -export const SIIBRA_API_VERSION = '0.1.13' - -const validVolumeType = new Set([ - 'neuroglancer/precomputed', - 'neuroglancer/precompmesh', - 'threesurfer/gii', - 'threesurfer/gii-label', -]) - -function getNgId(atlasId: string, tmplId: string, parcId: string, regionKey: string){ - const proxyId = MultiDimMap.GetProxyKeyMatch(atlasId, tmplId, parcId, regionKey) - if (proxyId) return proxyId - return '_' + MultiDimMap.GetKey(atlasId, tmplId, parcId, regionKey) -} - -function parseId(id: TId){ - if (typeof id === 'string') return id - return `${id.kg.kgSchema}/${id.kg.kgId}` -} - -type THasId = { - ['@id']: string - name: string -} - -type TIAVAtlas = { - templateSpaces: ({ availableIn: THasId[] } & THasId)[] - parcellations: ({ - availableIn: THasId[] - baseLayer: boolean - '@version': { - name: string - '@next': string - '@previous': string - '@this': string - } - } & THasId)[] -} & THasId - -type TNehubaConfig = Record<string, { - source: string - transform: number[][] - type: 'segmentation' | 'image' -}> - -type TViewerConfig = TNehubaConfig - -/** - * key value pair of - * atlasId -> templateId -> viewerConfig - */ -type TAtlasTmplViewerConfig = Record<string, Record<string, TViewerConfig>> - -export const spaceMiscInfoMap = new Map([ - ['minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588', { - name: 'bigbrain', - scale: 1, - }], - ['minds/core/referencespace/v1.0.0/dafcffc5-4826-4bf1-8ff6-46b8a31ff8e2', { - name: 'icbm2009c', - scale: 1, - }], - ['minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992', { - name: 'colin27', - scale: 1, - }], - ['minds/core/referencespace/v1.0.0/265d32a0-3d84-40a5-926f-bf89f68212b9', { - name: 'allen-mouse', - scale: 0.1, - }], - ['minds/core/referencespace/v1.0.0/d5717c4a-0fa1-46e6-918c-b8003069ade8', { - name: 'waxholm', - scale: 0.1, - }], -]) - -function getNehubaConfig(space: TSpaceFull) { - - const darkTheme = space.src_volume_type === 'mri' - const { scale } = spaceMiscInfoMap.get(space.id) || { scale: 1 } - const backgrd = darkTheme - ? [0,0,0,1] - : [1,1,1,1] - - const rmPsp = darkTheme - ? {"mode":"<","color":[0.1,0.1,0.1,1]} - :{"color":[1,1,1,1],"mode":"=="} - const drawSubstrates = darkTheme - ? {"color":[0.5,0.5,1,0.2]} - : {"color":[0,0,0.5,0.15]} - const drawZoomLevels = darkTheme - ? {"cutOff":150000 * scale } - : {"cutOff":200000 * scale,"color":[0.5,0,0,0.15] } - - // enable surface parcellation - // otherwise, on segmentation selection, the unselected meshes will also be invisible - const surfaceParcellation = space.id === 'minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992' - return { - "configName": "", - "globals": { - "hideNullImageValues": true, - "useNehubaLayout": { - "keepDefaultLayouts": false - }, - "useNehubaMeshLayer": true, - "rightClickWithCtrlGlobal": false, - "zoomWithoutCtrlGlobal": false, - "useCustomSegmentColors": true - }, - "zoomWithoutCtrl": true, - "hideNeuroglancerUI": true, - "rightClickWithCtrl": true, - "rotateAtViewCentre": true, - "enableMeshLoadingControl": true, - "zoomAtViewCentre": true, - // "restrictUserNavigation": true, - "dataset": { - "imageBackground": backgrd, - "initialNgState": { - "showDefaultAnnotations": false, - "layers": {}, - "navigation": { - "zoomFactor": 350000 * scale, - }, - "perspectiveOrientation": [ - 0.3140767216682434, - -0.7418519854545593, - 0.4988985061645508, - -0.3195493221282959 - ], - "perspectiveZoom": 1922235.5293810747 * scale - } - }, - "layout": { - "useNehubaPerspective": { - "perspectiveSlicesBackground": backgrd, - "removePerspectiveSlicesBackground": rmPsp, - "perspectiveBackground": backgrd, - "fixedZoomPerspectiveSlices": { - "sliceViewportWidth": 300, - "sliceViewportHeight": 300, - "sliceZoom": 563818.3562426177 * scale, - "sliceViewportSizeMultiplier": 2 - }, - "mesh": { - "backFaceColor": backgrd, - "removeBasedOnNavigation": true, - "flipRemovedOctant": true, - surfaceParcellation - }, - "centerToOrigin": true, - "drawSubstrates": drawSubstrates, - "drawZoomLevels": drawZoomLevels, - "restrictZoomLevel": { - "minZoom": 1200000 * scale, - "maxZoom": 3500000 * scale - } - } - } - } - -} - -@Injectable({ - providedIn: 'root' -}) - -export class PureContantService implements OnDestroy{ - - private subscriptions: Subscription[] = [] - public repoUrl = `https://github.com/FZJ-INM1-BDA/siibra-explorer` - public supportEmailAddress = `support@ebrains.eu` - public docUrl = `https://siibra-explorer.readthedocs.io/` - - public showHelpSupportText: string = `Did you encounter an issue? -Send us an email: <a target = "_blank" href = "mailto:${this.supportEmailAddress}">${this.supportEmailAddress}</a> - -Raise/track issues at github repo: <a target = "_blank" href = "${this.repoUrl}">${this.repoUrl}</a> -` - - public useTouchUI$: Observable<boolean> - public darktheme$: Observable<boolean> - - public totalAtlasesLength: number - - public allFetchingReady$: Observable<boolean> - - private atlasParcSpcRegionMap = new MultiDimMap() - - private _backendUrl = (BACKENDURL && `${BACKENDURL}/`.replace(/\/\/$/, '/')) || `${window.location.origin}${window.location.pathname}` - get backendUrl() { - console.warn(`something is using backendUrl`) - return this._backendUrl - } - - public getRegionDetail(atlasId: string, parcId: string, spaceId: string, region: any) { - return this.http.get<TRegionDetail>( - `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations/${encodeURIComponent(parcId)}/regions/${encodeURIComponent(region.name)}`, - { - params: { - 'space_id': spaceId - }, - responseType: 'json' - } - ) - } - - private patchRegions$ = forkJoin( - patchRegions.map(patch => from(patch)) - ).pipe( - shareReplay(1) - ) - - private getRegions(atlasId: string, parcId: string, spaceId: string){ - return this.http.get<TRegionSummary[]>( - `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations/${encodeURIComponent(parcId)}/regions`, - { - params: { - 'space_id': spaceId - }, - responseType: 'json' - } - ).pipe( - switchMap(regions => this.patchRegions$.pipe( - map(patchRegions => { - for (const p of patchRegions) { - if ( - p.targetParcellation !== '*' - && Array.isArray(p.targetParcellation) - && p.targetParcellation.every(p => p["@id"] !== parcId) - ) { - continue - } - if ( - p.targetSpace !== '*' - && Array.isArray(p.targetSpace) - && p.targetSpace.every(sp => sp['@id'] !== spaceId) - ) { - continue - } - - recursiveMutate( - regions, - r => r.children || [], - region => { - - if (p["@type"] === 'julich/siibra/append-region/v0.0.1') { - if (p.parent['name'] === region.name) { - if (!region.children) region.children = [] - region.children.push( - p.payload as TRegionSummary - ) - } - } - if (p['@type'] === 'julich/siibra/patch-region/v0.0.1') { - if (p.target['name'] === region.name) { - mutateDeepMerge( - region, - p.payload - ) - } - } - }, - true - ) - } - return regions - }) - )) - ) - } - - private getParcs(atlasId: string){ - return this.http.get<TParc[]>( - `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations`, - { responseType: 'json' } - ) - } - - private httpCallCache = new Map<string, Observable<any>>() - - private getParcDetail(atlasId: string, parcId: string) { - return this.http.get<TParc>( - `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/parcellations/${encodeURIComponent(parcId)}`, - { responseType: 'json' } - ) - } - - private getSpaces(atlasId: string){ - return this.http.get<TSpaceSummary[]>( - `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/spaces`, - { responseType: 'json' } - ) - } - - private getSpaceDetail(atlasId: string, spaceId: string) { - return this.http.get<TSpaceFull>( - `${this.bsEndpoint}/atlases/${encodeURIComponent(atlasId)}/spaces/${encodeURIComponent(spaceId)}`, - { responseType: 'json' } - ) - } - - private getSpacesAndParc(atlasId: string): Observable<{ templateSpaces: TSpaceFull[], parcellations: TParc[] }> { - const cacheKey = `getSpacesAndParc::${atlasId}` - if (this.httpCallCache.has(cacheKey)) return this.httpCallCache.get(cacheKey) - - const spaces$ = this.getSpaces(atlasId).pipe( - switchMap(spaces => spaces.length > 0 - ? forkJoin( - spaces.map(space => this.getSpaceDetail(atlasId, parseId(space.id))) - ) - : of([])) - ) - const parcs$ = this.getParcs(atlasId).pipe( - // need not to get full parc data. first level gets all data - // switchMap(parcs => forkJoin( - // parcs.map(parc => this.getParcDetail(atlasId, parseId(parc.id))) - // )) - ) - const returnObs = forkJoin([ - spaces$, - parcs$, - ]).pipe( - map(([ templateSpaces, parcellations ]) => { - /** - * select only parcellations that contain renderable volume(s) - */ - const filteredParcellations = parcellations.filter(p => { - return p._dataset_specs.some(spec => spec["@type"] === 'fzj/tmp/volume_type/v0.0.1' && validVolumeType.has(spec.volume_type)) - }) - - /** - * remove parcellation versions that are marked as deprecated - * and assign prev/next id accordingly - */ - for (const p of filteredParcellations) { - if (!p.version) continue - if (p.version.deprecated) { - const prevId = p.version.prev - const nextId = p.version.next - - const prev = prevId && filteredParcellations.find(p => parseId(p.id) === prevId) - const next = nextId && filteredParcellations.find(p => parseId(p.id) === nextId) - - const newPrevId = prev && parseId(prev.id) - const newNextId = next && parseId(next.id) - - if (!!prev.version) { - prev.version.next = newNextId - } - - if (!!next.version) { - next.version.prev = newPrevId - } - } - } - const removeDeprecatedParc = filteredParcellations.filter(p => { - if (!p.version) return true - return !(p.version.deprecated) - }) - - return { - templateSpaces, - parcellations: removeDeprecatedParc - } - }), - shareReplay(1) - ) - this.httpCallCache.set(cacheKey, returnObs) - return returnObs - } - - constructor( - private store: Store<any>, - private http: HttpClient, - private log: LoggingService, - private snackbar: MatSnackBar, - @Inject(BS_ENDPOINT) private bsEndpoint: string, - ){ - this.darktheme$ = this.store.pipe( - select(viewerStateSelectedTemplateSelector), - map(tmpl => tmpl?.useTheme === 'dark') - ) - - this.useTouchUI$ = this.store.pipe( - select(viewerConfigSelectorUseMobileUi), - shareReplay(1) - ) - - this.subscriptions.push( - this.fetchedAtlases$.subscribe(fetchedAtlases => - this.store.dispatch( - viewerStateSetFetchedAtlases({ fetchedAtlases }) - ) - ) - ) - - this.allFetchingReady$ = combineLatest([ - this.initFetchTemplate$.pipe( - filter(v => !!v), - map(arr => arr.length), - ), - this.store.pipe( - select(viewerStateFetchedTemplatesSelector), - map(arr => arr.length), - ), - this.store.pipe( - select(viewerStateFetchedAtlasesSelector), - map(arr => arr.length), - ) - ]).pipe( - map(([ expNumTmpl, actNumTmpl, actNumAtlas ]) => { - return expNumTmpl === actNumTmpl && actNumAtlas === this.totalAtlasesLength - }), - distinctUntilChanged(), - shareReplay(1), - ) - } - - private getAtlases$ = this.http.get<TAtlas[]>( - `${this.bsEndpoint}/atlases`, - { - observe: 'response' - } - ).pipe( - tap(resp => { - const respVersion = resp.headers.get(SIIBRA_API_VERSION_HEADER_KEY) - if (respVersion !== SIIBRA_API_VERSION) { - this.snackbar.open(`Expecting ${SIIBRA_API_VERSION}, got ${respVersion}. Some functionalities may not work as expected.`, 'Dismiss', { - duration: 5000 - }) - } - console.log(`siibra-api::version::${respVersion}, expecting::${SIIBRA_API_VERSION}`) - }), - map(resp => { - const arr = resp.body - const { EXPERIMENTAL_FEATURE_FLAG } = environment - if (EXPERIMENTAL_FEATURE_FLAG) return arr - return arr - }), - shareReplay(1), - ) - - public fetchedAtlases$: Observable<TIAVAtlas[]> = this.getAtlases$.pipe( - switchMap(atlases => { - return forkJoin( - atlases.map( - atlas => this.getSpacesAndParc(atlas.id).pipe( - map(({ templateSpaces, parcellations }) => { - return { - '@id': atlas.id, - name: atlas.name, - templateSpaces: templateSpaces.map(tmpl => { - return { - '@id': tmpl.id, - name: tmpl.name, - availableIn: tmpl.availableParcellations.map(parc => { - return { - '@id': parc.id, - name: parc.name - } - }), - originDatainfos: (tmpl._dataset_specs || []).filter(spec => spec["@type"] === 'fzj/tmp/simpleOriginInfo/v0.0.1') - } - }), - parcellations: parcellations.filter(p => { - if (p.version?.deprecated) return false - return true - }).map(parc => { - return { - '@id': parseId(parc.id), - name: parc.name, - baseLayer: parc.modality === 'cytoarchitecture', - '@version': { - '@next': parc.version?.next, - '@previous': parc.version?.prev, - 'name': parc.version?.name, - '@this': parseId(parc.id) - }, - groupName: parc.modality || null, - availableIn: parc.availableSpaces.map(space => { - return { - '@id': space.id, - name: space.name, - /** - * TODO need original data format - */ - // originalDatasetFormats: [{ - // name: "probability map" - // }] - } - }), - originDatainfos: [...(parc.infos || []), ...(parc._dataset_specs || []).filter(spec => spec["@type"] === 'fzj/tmp/simpleOriginInfo/v0.0.1')] - } - }) - } - }), - catchError((err, obs) => { - console.error(err) - return of(null) - }) - ) - ) - ) - }), - catchError((err, obs) => of([])), - tap((arr: any[]) => this.totalAtlasesLength = arr.length), - scan((acc, curr) => acc.concat(curr).sort((a, b) => (a.order || 0) - (b.order || 0)), []), - shareReplay(1) - ) - - private atlasTmplConfig: TAtlasTmplViewerConfig = {} - - async getViewerConfig(atlasId: string, templateId: string, parcId: string) { - const atlasLayers = this.atlasTmplConfig[atlasId] - const templateLayers = atlasLayers && atlasLayers[templateId] - return templateLayers || {} - } - - public initFetchTemplate$ = this.fetchedAtlases$.pipe( - switchMap(atlases => { - return forkJoin( - atlases.map(atlas => this.getSpacesAndParc(atlas['@id']).pipe( - switchMap(({ templateSpaces, parcellations }) => { - this.atlasTmplConfig[atlas["@id"]] = {} - return forkJoin( - templateSpaces.map( - tmpl => { - // hardcode - // see https://github.com/FZJ-INM1-BDA/siibra-python/issues/98 - if ( - tmpl.id === 'minds/core/referencespace/v1.0.0/tmp-fsaverage' - && !tmpl.availableParcellations.find(p => p.id === 'minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290') - ) { - tmpl.availableParcellations.push({ - id: 'minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290', - name: 'Julich-Brain Probabilistic Cytoarchitectonic Maps (v2.9)' - }) - } - this.atlasTmplConfig[atlas["@id"]][tmpl.id] = {} - return tmpl.availableParcellations.map( - parc => this.getRegions(atlas['@id'], parc.id, tmpl.id).pipe( - tap(regions => { - recursiveMutate( - regions, - region => region.children, - region => { - /** - * individual map(s) - * this should work for both fully mapped and interpolated - * in the case of interpolated, it sucks that the ngLayerObj will be set multiple times - */ - - const dedicatedMap = region._dataset_specs.filter( - spec => spec["@type"] === 'fzj/tmp/volume_type/v0.0.1' - && spec.space_id === tmpl.id - && spec['volume_type'] === 'neuroglancer/precomputed' - ) as TVolumeSrc<'neuroglancer/precomputed'>[] - if (dedicatedMap.length === 1) { - const ngId = getNgId(atlas['@id'], tmpl.id, parc.id, dedicatedMap[0]['@id']) - region['ngId'] = ngId - region['labelIndex'] = dedicatedMap[0].detail['neuroglancer/precomputed'].labelIndex - this.atlasTmplConfig[atlas["@id"]][tmpl.id][ngId] = { - source: `precomputed://${dedicatedMap[0].url}`, - type: "segmentation", - transform: dedicatedMap[0].detail['neuroglancer/precomputed'].transform - } - } - - /** - * if label index is defined - */ - if (!!region.labelIndex) { - const hemisphereKey = /left hemisphere|left/.test(region.name) - // these two keys are, unfortunately, more or less hardcoded - // which is less than ideal - ? 'left hemisphere' - : /right hemisphere|right/.test(region.name) - ? 'right hemisphere' - : 'whole brain' - - if (!region['ngId']) { - const hemispheredNgId = getNgId(atlas['@id'], tmpl.id, parc.id, hemisphereKey) - region['ngId'] = hemispheredNgId - } - } - } - ) - this.atlasParcSpcRegionMap.set( - atlas['@id'], tmpl.id, parc.id, regions - ) - - /** - * populate maps for parc - */ - for (const parc of parcellations) { - const precomputedVols = parc._dataset_specs.filter( - spec => spec["@type"] === 'fzj/tmp/volume_type/v0.0.1' - && spec.volume_type === 'neuroglancer/precomputed' - && spec.space_id === tmpl.id - ) as TVolumeSrc<'neuroglancer/precomputed'>[] - - if (precomputedVols.length === 1) { - const vol = precomputedVols[0] - const key = 'whole brain' - - const ngIdKey = getNgId(atlas['@id'], tmpl.id, parseId(parc.id), key) - this.atlasTmplConfig[atlas["@id"]][tmpl.id][ngIdKey] = { - source: `precomputed://${vol.url}`, - type: "segmentation", - transform: vol.detail['neuroglancer/precomputed'].transform - } - } - - if (precomputedVols.length === 2) { - const mapIndexKey = [{ - mapIndex: 0, - key: 'left hemisphere' - }, { - mapIndex: 1, - key: 'right hemisphere' - }] - for (const { key, mapIndex } of mapIndexKey) { - const ngIdKey = getNgId(atlas['@id'], tmpl.id, parseId(parc.id), key) - this.atlasTmplConfig[atlas["@id"]][tmpl.id][ngIdKey] = { - source: `precomputed://${precomputedVols[mapIndex].url}`, - type: "segmentation", - transform: precomputedVols[mapIndex].detail['neuroglancer/precomputed'].transform - } - } - } - - if (precomputedVols.length > 2) { - console.error(`precomputedVols.length > 0, most likely an error`) - } - } - }), - catchError((err, obs) => { - return of(null) - }) - ) - ) - } - ).reduce(flattenReducer, []) - ).pipe( - mapTo({ templateSpaces, parcellations, ngLayerObj: this.atlasTmplConfig }) - ) - }), - map(({ templateSpaces, parcellations, ngLayerObj }) => { - return templateSpaces.map(tmpl => { - - // configuring three-surfer - let threeSurferConfig = {} - const volumes = tmpl._dataset_specs.filter(v => v["@type"] === 'fzj/tmp/volume_type/v0.0.1') as TVolumeSrc<keyof IVolumeTypeDetail>[] - const threeSurferVolSrc = volumes.find(v => v.volume_type === 'threesurfer/gii') - if (threeSurferVolSrc) { - const foundP = parcellations.find(p => { - return p._dataset_specs.some(spec => spec["@type"] === 'fzj/tmp/volume_type/v0.0.1' && spec.space_id === tmpl.id) - }) - const url = threeSurferVolSrc.url - const { surfaces } = threeSurferVolSrc.detail['threesurfer/gii'] as { surfaces: {mode: string, hemisphere: 'left' | 'right', url: string}[] } - const modObj = {} - for (const surface of surfaces) { - - const hemisphereKey = surface.hemisphere === 'left' - ? 'left hemisphere' - : 'right hemisphere' - - - /** - * concating all available gii maps - */ - // const allFreesurferLabels = foundP.volumeSrc[tmpl.id][hemisphereKey].filter(v => v.volume_type === 'threesurfer/gii-label') - // for (const lbl of allFreesurferLabels) { - // const modeToConcat = { - // mesh: surface.url, - // hemisphere: surface.hemisphere, - // colormap: lbl.url - // } - - // const key = `${surface.mode} - ${lbl.name}` - // if (!modObj[key]) { - // modObj[key] = [] - // } - // modObj[key].push(modeToConcat) - // } - - /** - * only concat first matching gii map - */ - const mapIndex = hemisphereKey === 'left hemisphere' - ? 0 - : 1 - const labelMaps = foundP._dataset_specs.filter(spec => spec["@type"] === 'fzj/tmp/volume_type/v0.0.1' && spec.volume_type === 'threesurfer/gii-label') as TVolumeSrc<'threesurfer/gii-label'>[] - const key = surface.mode - const modeToConcat = { - mesh: surface.url, - hemisphere: surface.hemisphere, - colormap: (() => { - const lbl = labelMaps[mapIndex] - return lbl?.url - })() - } - if (!modObj[key]) { - modObj[key] = [] - } - modObj[key].push(modeToConcat) - - } - foundP[tmpl.id] - threeSurferConfig = { - "three-surfer": { - '@context': { - root: url - }, - modes: Object.keys(modObj).map(name => { - return { - name, - meshes: modObj[name] - } - }) - }, - nehubaConfig: null, - nehubaConfigURL: null, - useTheme: 'dark' - } - } - const darkTheme = tmpl.src_volume_type === 'mri' - const nehubaConfig = getNehubaConfig(tmpl) - const initialLayers = nehubaConfig.dataset.initialNgState.layers - - const tmplAuxMesh = `${tmpl.name} auxmesh` - - const precomputedArr = tmpl._dataset_specs.filter(src => src['@type'] === 'fzj/tmp/volume_type/v0.0.1' && src.volume_type === 'neuroglancer/precomputed') as TVolumeSrc<'neuroglancer/precomputed'>[] - let visible = true - let tmplNgId: string - const templateImages: TTemplateImage[] = [] - for (const precomputedItem of precomputedArr) { - const ngIdKey = MultiDimMap.GetKey(precomputedItem["@id"]) - const precomputedUrl = 'https://neuroglancer.humanbrainproject.eu/precomputed/data-repo-ng-bot/20211001-mebrain/precomputed/images/MEBRAINS_T1.masked' === precomputedItem.url - ? 'https://neuroglancer.humanbrainproject.eu/precomputed/data-repo-ng-bot/20211018-mebrains-masked-templates/precomputed/images/MEBRAINS_T1_masked' - : precomputedItem.url - initialLayers[ngIdKey] = { - type: "image", - source: `precomputed://${precomputedUrl}`, - transform: precomputedItem.detail['neuroglancer/precomputed'].transform, - visible - } - templateImages.push({ - "@id": precomputedItem['@id'], - name: precomputedItem.name, - ngId: ngIdKey, - visible - }) - if (visible) { - tmplNgId = ngIdKey - } - visible = false - } - - // TODO - // siibra-python accidentally left out volume type of precompmesh - // https://github.com/FZJ-INM1-BDA/siibra-python/pull/55 - // use url to determine for now - // const precompmesh = tmpl.volume_src.find(src => src.volume_type === 'neuroglancer/precompmesh') - const precompmesh = tmpl._dataset_specs.find(src => src["@type"] === 'fzj/tmp/volume_type/v0.0.1' && !!src.detail?.['neuroglancer/precompmesh']) as TVolumeSrc<'neuroglancer/precompmesh'> - const auxMeshes = [] - if (precompmesh){ - initialLayers[tmplAuxMesh] = { - source: `precompmesh://${precompmesh.url}`, - type: "segmentation", - transform: precompmesh.detail['neuroglancer/precompmesh'].transform - } - for (const auxMesh of precompmesh.detail['neuroglancer/precompmesh'].auxMeshes) { - - auxMeshes.push({ - ...auxMesh, - ngId: tmplAuxMesh, - '@id': `${tmplAuxMesh} ${auxMesh.name}`, - visible: true - }) - } - } - - for (const key in (ngLayerObj[atlas["@id"]][tmpl.id] || {})) { - initialLayers[key] = ngLayerObj[atlas["@id"]][tmpl.id][key] - } - - return { - name: tmpl.name, - '@id': tmpl.id, - fullId: tmpl.id, - useTheme: darkTheme ? 'dark' : 'light', - ngId: tmplNgId, - nehubaConfig, - templateImages, - auxMeshes, - /** - * only populate the parcelltions made available - */ - parcellations: tmpl.availableParcellations.filter( - p => parcellations.some(p2 => parseId(p2.id) === p.id) - ).map(parc => { - const fullParcInfo = parcellations.find(p => parseId(p.id) === parc.id) - const regions = this.atlasParcSpcRegionMap.get(atlas['@id'], tmpl.id, parc.id) || [] - return { - fullId: parc.id, - '@id': parc.id, - name: parc.name, - regions, - originDatainfos: [...fullParcInfo.infos, ...(fullParcInfo?._dataset_specs || []).filter(spec => spec["@type"] === 'fzj/tmp/simpleOriginInfo/v0.0.1')] - } - }), - ...threeSurferConfig - } - }) - }) - )) - ) - }), - map(arr => { - return arr.reduce(flattenReducer, []) - }), - catchError((err) => { - this.log.warn(`fetching templates error`, err) - return of(null) - }), - shareReplay(1), - ) - - ngOnDestroy(){ - while(this.subscriptions.length > 0) this.subscriptions.pop().unsubscribe() - } -} diff --git a/src/util/util.module.ts b/src/util/util.module.ts index f128cf3fd266a1851d2951bf21c78ffffd4cedfd..b1007281e3c037b4766b301c0189e15dca21a878 100644 --- a/src/util/util.module.ts +++ b/src/util/util.module.ts @@ -1,13 +1,10 @@ import { NgModule } from "@angular/core"; -import { FilterRowsByVisbilityPipe } from "src/components/flatTree/filterRowsByVisibility.pipe"; import { KeyListner } from "./directives/keyDownListener.directive"; import { StopPropagationDirective } from "./directives/stopPropagation.directive"; -import { IncludesPipe } from "./pipes/includes.pipe"; import { SafeResourcePipe } from "./pipes/safeResource.pipe"; import { CaptureClickListenerDirective } from "./directives/captureClickListener.directive"; -import { AddUnitAndJoin } from "./pipes/addUnitAndJoin.pipe"; -import { NmToMm } from "./pipes/numbers.pipe"; +import { NmToMm } from "./pipes/nmToMm.pipe"; import { SwitchDirective } from "./directives/switch.directive"; import { MediaQueryDirective } from './directives/mediaQuery.directive' import { LayoutModule } from "@angular/cdk/layout"; @@ -28,13 +25,10 @@ import { MergeObjPipe } from "./mergeObj.pipe"; LayoutModule ], declarations: [ - FilterRowsByVisbilityPipe, StopPropagationDirective, KeyListner, - IncludesPipe, SafeResourcePipe, CaptureClickListenerDirective, - AddUnitAndJoin, NmToMm, SwitchDirective, MediaQueryDirective, @@ -51,13 +45,10 @@ import { MergeObjPipe } from "./mergeObj.pipe"; MergeObjPipe, ], exports: [ - FilterRowsByVisbilityPipe, StopPropagationDirective, KeyListner, - IncludesPipe, SafeResourcePipe, CaptureClickListenerDirective, - AddUnitAndJoin, NmToMm, SwitchDirective, MediaQueryDirective, diff --git a/src/util/windowResize/windowResize.directive.ts b/src/util/windowResize/windowResize.directive.ts index ce8de31804fcaec53296925c5eef5235979719b0..2a8f83fd99b19c883d6060c2709f371b5a4210cb 100644 --- a/src/util/windowResize/windowResize.directive.ts +++ b/src/util/windowResize/windowResize.directive.ts @@ -1,4 +1,4 @@ -import { Directive, EventEmitter, Input, OnChanges, OnInit, Output } from "@angular/core"; +import { Directive, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from "@angular/core"; import { Subscription } from "rxjs"; import { ResizeObserverService } from "./windowResize.service"; @@ -7,7 +7,7 @@ import { ResizeObserverService } from "./windowResize.service"; exportAs: 'iavWindowResize' }) -export class ResizeObserverDirective implements OnChanges, OnInit { +export class ResizeObserverDirective implements OnChanges, OnInit, OnDestroy { @Input('iav-window-resize-type') type: 'debounce' | 'throttle' = 'throttle' @@ -27,14 +27,18 @@ export class ResizeObserverDirective implements OnChanges, OnInit { constructor(private svc: ResizeObserverService){} - ngOnInit(){ + ngOnInit(): void { this.configure() } - ngOnChanges(){ + ngOnChanges(): void { this.configure() } - configure(){ + ngOnDestroy(): void { + while(this.sub.length > 0) this.sub.pop().unsubscribe() + } + + configure(): void { while(this.sub.length > 0) this.sub.pop().unsubscribe() let sub: Subscription diff --git a/src/util/windowResize/windowResize.service.ts b/src/util/windowResize/windowResize.service.ts index f50c7df0e929a935b56176311b887d585d6412eb..f48a5832517f9e770356deab0f131e1b167b36a8 100644 --- a/src/util/windowResize/windowResize.service.ts +++ b/src/util/windowResize/windowResize.service.ts @@ -1,6 +1,6 @@ import { Injectable } from "@angular/core"; -import { asyncScheduler, fromEvent } from "rxjs"; -import { debounceTime, shareReplay, tap, throttleTime } from "rxjs/operators"; +import { asyncScheduler, fromEvent, Observable } from "rxjs"; +import { debounceTime, shareReplay, throttleTime } from "rxjs/operators"; interface IThrottleConfig { leading: boolean @@ -16,13 +16,13 @@ export class ResizeObserverService { shareReplay(1) ) - public getThrottledResize(time: number, config?: IThrottleConfig){ + public getThrottledResize(time: number, config?: IThrottleConfig) : Observable<Event>{ return this.windowResize.pipe( throttleTime(time, asyncScheduler, config || { leading: false, trailing: true }), ) } - public getDebouncedResize(time: number) { + public getDebouncedResize(time: number): Observable<Event> { return this.windowResize.pipe( debounceTime(time) ) diff --git a/src/viewerModule/componentStore.ts b/src/viewerModule/componentStore.ts index 5fe24c51085c329be8ed3eaee2048f3114cbda43..3e6d6e79b6e6942b44ebf8ad7ef4b5a44f166f74 100644 --- a/src/viewerModule/componentStore.ts +++ b/src/viewerModule/componentStore.ts @@ -1,6 +1,6 @@ import { Injectable } from "@angular/core"; import { select } from "@ngrx/store"; -import { ReplaySubject, Subject } from "rxjs"; +import { Observable, ReplaySubject, Subject } from "rxjs"; import { shareReplay } from "rxjs/operators"; export class LockError extends Error{} @@ -15,14 +15,14 @@ export class LockError extends Error{} export class ComponentStore<T>{ private _state$: Subject<T> = new ReplaySubject<T>(1) private _lock: boolean = false - get isLocked() { + get isLocked(): boolean { return this._lock } - setState(state: T){ + setState(state: T): void { if (this.isLocked) throw new LockError('State is locked') this._state$.next(state) } - select<V>(selectorFn: (state: T) => V) { + select<V>(selectorFn: (state: T) => V): Observable<V> { return this._state$.pipe( select(selectorFn), shareReplay(1), diff --git a/src/viewerModule/constants.ts b/src/viewerModule/constants.ts index 8fa2d25231b1435428d4ca4f652617904fcc95bc..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/src/viewerModule/constants.ts +++ b/src/viewerModule/constants.ts @@ -1,4 +0,0 @@ -import { InjectionToken } from "@angular/core"; -import { Observable } from "rxjs"; - -export const VIEWERMODULE_DARKTHEME = new InjectionToken<Observable<boolean>>('VIEWERMODULE_DARKTHEME') diff --git a/src/viewerModule/index.ts b/src/viewerModule/index.ts index 53f950d5421d8dbf54ce6482c72be6dddfe9c377..6e5c74fbe55be040c17b54d98c7b6f29cd66a513 100644 --- a/src/viewerModule/index.ts +++ b/src/viewerModule/index.ts @@ -1,2 +1 @@ -export { ViewerModule } from "./module" -export { VIEWERMODULE_DARKTHEME } from './constants' \ No newline at end of file +export { ViewerModule } from "./module" \ No newline at end of file diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts index 8cd43cf6ace30f25be8afea1cffe58be1c175881..14779f2a3f322e0162e88c35a615adf78482aec7 100644 --- a/src/viewerModule/module.ts +++ b/src/viewerModule/module.ts @@ -1,34 +1,31 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { Observable } from "rxjs"; -import { AtlasCmpParcellationModule } from "src/atlasComponents/parcellation"; -import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; -import { BSFeatureModule, BS_DARKTHEME, } from "src/atlasComponents/regionalFeatures/bsFeatures"; -import { SplashUiModule } from "src/atlasComponents/splashScreen"; -import { AtlasCmpUiSelectorsModule } from "src/atlasComponents/uiSelectors"; import { ComponentsModule } from "src/components"; import { ContextMenuModule, ContextMenuService, TContextMenuReg } from "src/contextMenuModule"; import { LayoutModule } from "src/layouts/layout.module"; import { AngularMaterialModule } from "src/sharedModules"; import { TopMenuModule } from "src/ui/topMenu/module"; import { CONTEXT_MENU_ITEM_INJECTOR, TContextMenu, UtilModule } from "src/util"; -import { VIEWERMODULE_DARKTHEME } from "./constants"; import { NehubaModule, NehubaViewerUnit } from "./nehuba"; import { ThreeSurferModule } from "./threeSurfer"; import { ViewerCmp } from "./viewerCmp/viewerCmp.component"; -import {UserAnnotationsModule} from "src/atlasComponents/userAnnotations"; -import {QuickTourModule} from "src/ui/quickTour/module"; +import { UserAnnotationsModule } from "src/atlasComponents/userAnnotations"; +import { QuickTourModule } from "src/ui/quickTour/module"; import { INJ_ANNOT_TARGET } from "src/atlasComponents/userAnnotations/tools/type"; import { NEHUBA_INSTANCE_INJTKN } from "./nehuba/util"; import { map } from "rxjs/operators"; import { TContextArg } from "./viewer.interface"; -import { ViewerStateBreadCrumbModule } from "./viewerStateBreadCrumb/module"; -import { KgRegionalFeatureModule } from "src/atlasComponents/regionalFeatures/bsFeatures/kgRegionalFeature"; -import { API_SERVICE_SET_VIEWER_HANDLE_TOKEN, AtlasViewerAPIServices, setViewerHandleFactory } from "src/atlasViewer/atlasViewer.apiService.service"; -import { ILoadMesh, LOAD_MESH_TOKEN } from "src/messaging/types"; import { KeyFrameModule } from "src/keyframesModule/module"; import { ViewerInternalStateSvc } from "./viewerInternalState.service"; -import { LayerBrowserModule } from "src/ui/layerbrowser"; +import { SAPIModule } from 'src/atlasComponents/sapi'; +import { NehubaVCtxToBbox } from "./pipes/nehubaVCtxToBbox.pipe"; +import { SapiViewsModule, SapiViewsUtilModule } from "src/atlasComponents/sapiViews"; +import { DialogModule } from "src/ui/dialogInfo/module"; +import { MouseoverModule } from "src/mouseoverModule"; +import { LogoContainer } from "src/ui/logoContainer/logoContainer.component"; +import { FloatingMouseContextualContainerDirective } from "src/util/directives/floatingMouseContextualContainer.directive"; +import { ShareModule } from "src/share"; @NgModule({ imports: [ @@ -36,34 +33,28 @@ import { LayerBrowserModule } from "src/ui/layerbrowser"; NehubaModule, ThreeSurferModule, LayoutModule, - AtlasCmpUiSelectorsModule, AngularMaterialModule, - SplashUiModule, TopMenuModule, - ParcellationRegionModule, UtilModule, - AtlasCmpParcellationModule, ComponentsModule, - BSFeatureModule, UserAnnotationsModule, QuickTourModule, ContextMenuModule, - ViewerStateBreadCrumbModule, - KgRegionalFeatureModule, KeyFrameModule, - LayerBrowserModule, + SAPIModule, + SapiViewsModule, + SapiViewsUtilModule, + DialogModule, + MouseoverModule, + ShareModule, ], declarations: [ ViewerCmp, + NehubaVCtxToBbox, + LogoContainer, + FloatingMouseContextualContainerDirective, ], providers: [ - { - provide: BS_DARKTHEME, - useFactory: (obs$: Observable<boolean>) => obs$, - deps: [ - VIEWERMODULE_DARKTHEME - ] - }, { provide: INJ_ANNOT_TARGET, useFactory: (obs$: Observable<NehubaViewerUnit>) => { @@ -85,21 +76,6 @@ import { LayerBrowserModule } from "src/ui/layerbrowser"; }, deps: [ ContextMenuService ] }, - { - provide: API_SERVICE_SET_VIEWER_HANDLE_TOKEN, - useFactory: setViewerHandleFactory, - deps: [ AtlasViewerAPIServices ] - }, - { - provide: LOAD_MESH_TOKEN, - useFactory: (apiService: AtlasViewerAPIServices) => { - return (loadMeshParam: ILoadMesh) => apiService.loadMesh$.next(loadMeshParam) - }, - deps: [ - AtlasViewerAPIServices - ] - }, - AtlasViewerAPIServices, ViewerInternalStateSvc, ], exports: [ diff --git a/src/viewerModule/nehuba/annotation/effects.spec.ts b/src/viewerModule/nehuba/annotation/effects.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..6d18fc4adfdfab0a6e1b45b939be6f95748fe134 --- /dev/null +++ b/src/viewerModule/nehuba/annotation/effects.spec.ts @@ -0,0 +1,60 @@ +import { TestBed } from "@angular/core/testing" +import { provideMockActions } from "@ngrx/effects/testing" +import { Action } from "@ngrx/store" +import { MockStore, provideMockStore } from "@ngrx/store/testing" +import { hot } from "jasmine-marbles" +import { Observable } from "rxjs" +import { annotation, atlasAppearance } from "src/state" +import { NgAnnotationEffects } from "./effects" + +describe("effects.ts", () => { + describe("NgAnnotationEffects", () => { + let actions$: Observable<Action> + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + provideMockStore(), + provideMockActions(() => actions$), + NgAnnotationEffects, + ] + }) + }) + describe("onAnnotationHideQuadrant", () => { + describe("> when space filtered annotation does not exist", () => { + it("> should setOctantRemoval true", () => { + const mockStore = TestBed.inject(MockStore) + mockStore.overrideSelector(annotation.selectors.spaceFilteredAnnotations, []) + const effect = TestBed.inject(NgAnnotationEffects) + expect(effect.onAnnotationHideQuadrant).toBeObservable( + hot('a', { + a: atlasAppearance.actions.setOctantRemoval({ + flag: true + }) + }) + ) + }) + }) + describe("> when space filtered annotation exist", () => { + it("> should setOctantRemoval false", () => { + const mockStore = TestBed.inject(MockStore) + mockStore.overrideSelector(annotation.selectors.spaceFilteredAnnotations, [{} as any]) + const effect = TestBed.inject(NgAnnotationEffects) + expect(effect.onAnnotationHideQuadrant).toBeObservable( + hot('a', { + a: atlasAppearance.actions.setOctantRemoval({ + flag: false + }) + }) + ) + }) + }) + + describe("> on switch of space filtered annotations length", () => { + it("> should emit accordingly") + }) + describe("> on repeated emit of space filtered annotations length", () => { + it("> should only emit once") + }) + }) + }) +}) \ No newline at end of file diff --git a/src/viewerModule/nehuba/annotation/effects.ts b/src/viewerModule/nehuba/annotation/effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..10c510db4a0133b41962613926b72ea7159884f4 --- /dev/null +++ b/src/viewerModule/nehuba/annotation/effects.ts @@ -0,0 +1,21 @@ +import { Injectable } from "@angular/core"; +import { createEffect } from "@ngrx/effects"; +import { select, Store } from "@ngrx/store"; +import { distinctUntilChanged, map } from "rxjs/operators"; +import { annotation, atlasAppearance } from "src/state" + +@Injectable() +export class NgAnnotationEffects{ + constructor(private store: Store){} + + onAnnotationHideQuadrant = createEffect(() => this.store.pipe( + select(annotation.selectors.spaceFilteredAnnotations), + map(arr => arr.length > 0), + distinctUntilChanged(), + map(spaceFilteredAnnotationExists => { + return atlasAppearance.actions.setOctantRemoval({ + flag: !spaceFilteredAnnotationExists + }) + }), + )) +} diff --git a/src/viewerModule/nehuba/annotation/service.ts b/src/viewerModule/nehuba/annotation/service.ts new file mode 100644 index 0000000000000000000000000000000000000000..fcc109e8f6cc6b81f561e48c42c8123f62107a3e --- /dev/null +++ b/src/viewerModule/nehuba/annotation/service.ts @@ -0,0 +1,91 @@ +import { Injectable, OnDestroy } from "@angular/core"; +import { select, Store } from "@ngrx/store"; +import { Subscription } from "rxjs"; +import { filter, map, pairwise, startWith } from "rxjs/operators"; +import { AnnotationLayer, TNgAnnotationAABBox, TNgAnnotationLine, TNgAnnotationPoint } from "src/atlasComponents/annotations"; +import { annotation } from "src/state"; + + +@Injectable({ + providedIn: 'root' +}) + +export class NgAnnotationService implements OnDestroy { + + private subs: Subscription[] = [] + static ANNOTATION_LAYER_NAME = 'whale-annotation-layer' + static INIT_LAYERS: Set<string> = new Set<string>() + static COLOR_MAP = new Map<keyof typeof annotation.AnnotationColor, string>([ + [annotation.AnnotationColor.WHITE, "#ffffff"], + [annotation.AnnotationColor.BLUE, "#00ff00"], + [annotation.AnnotationColor.RED, "#ff0000"], + ]) + + static GET_ANN_LAYER(ann: annotation.UnionAnnotation): AnnotationLayer { + const color = ann.color || annotation.AnnotationColor.WHITE + const layerEnum = annotation.AnnotationColor[color] || annotation.AnnotationColor.WHITE + const layerName = `${NgAnnotationService.ANNOTATION_LAYER_NAME}-${layerEnum}` + + const annLayer = AnnotationLayer.Get(layerName, NgAnnotationService.COLOR_MAP.get(color) || "#ffffff") + NgAnnotationService.INIT_LAYERS.add(layerName) + return annLayer + } + + ngOnDestroy(): void { + NgAnnotationService.INIT_LAYERS.forEach(layername => { + const layer = AnnotationLayer.Get(layername, '#ffffff') + layer.dispose() + }) + NgAnnotationService.INIT_LAYERS.clear() + while(this.subs.length) this.subs.pop().unsubscribe() + } + + constructor( + private store: Store + ){ + + this.subs.push( + this.store.pipe( + select(annotation.selectors.spaceFilteredAnnotations) + ).pipe( + startWith<annotation.UnionAnnotation[]>([]), + pairwise(), + map(([prevAnnotations, currAnnotations]) => { + const prevAnnotationIds = new Set(prevAnnotations.map(ann => ann["@id"])) + const currAnnotationIds = new Set(currAnnotations.map(ann => ann["@id"])) + const newAnnotations = currAnnotations.filter(ann => !prevAnnotationIds.has(ann["@id"])) + const expiredAnnotations = prevAnnotations.filter(ann => !currAnnotationIds.has(ann["@id"])) + return { + newAnnotations, + expiredAnnotations + } + }), + filter(({ newAnnotations, expiredAnnotations }) => newAnnotations.length > 0 || expiredAnnotations.length > 0) + ).subscribe(({ newAnnotations, expiredAnnotations }) => { + + for (const ann of expiredAnnotations) { + const annLayer = NgAnnotationService.GET_ANN_LAYER(ann) + annLayer.removeAnnotation({ + id: ann["@id"] + }) + } + for (const ann of newAnnotations) { + let annotation: TNgAnnotationPoint | TNgAnnotationLine | TNgAnnotationAABBox + let annLayer: AnnotationLayer + if (!!ann['openminds']) { + annLayer = NgAnnotationService.GET_ANN_LAYER(ann) + annotation = { + id: ann["@id"], + description: ann.description, + type: 'point', + point: (ann as annotation.Annotation<'openminds'>).openminds.coordinates.map(coord => coord.value * 1e6) as [number, number, number] + } as TNgAnnotationPoint + } + + if (annotation && annLayer) annLayer.addAnnotation(annotation) + else console.warn(`annotation or annotation layer was not initialized.`) + } + }) + ) + } +} diff --git a/src/viewerModule/nehuba/config.service/config.service.spec.ts b/src/viewerModule/nehuba/config.service/config.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/viewerModule/nehuba/config.service/index.ts b/src/viewerModule/nehuba/config.service/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1d0fd090589abf2a94209c68e8da7eff87c9b630 --- /dev/null +++ b/src/viewerModule/nehuba/config.service/index.ts @@ -0,0 +1,18 @@ +export { + NehubaConfig, + NgConfig, + NgConfigViewerState, + NgLayerSpec, + NgPrecompMeshSpec, + NgSegLayerSpec, +} from "./type" +export { + getParcNgLayers, + getTmplAuxNgLayer, + getTmplNgLayer, + getNgLayersFromVolumesATP, + getParcNgId, + getRegionLabelIndex, + getNehubaConfig, + defaultNehubaConfig, +} from "./util" diff --git a/src/viewerModule/nehuba/config.service/type.ts b/src/viewerModule/nehuba/config.service/type.ts new file mode 100644 index 0000000000000000000000000000000000000000..c8154dd44432e6efe3d7920977f5dd287c557726 --- /dev/null +++ b/src/viewerModule/nehuba/config.service/type.ts @@ -0,0 +1,115 @@ + +export type RecursivePartial<T> = { + [K in keyof T]?: RecursivePartial<T[K]> +} + +type Vec4 = number[] +type Vec3 = number[] + +export type NgConfigViewerState = { + perspectiveOrientation: Vec4 + perspectiveZoom: number + navigation: { + pose: { + position: { + voxelSize: Vec3 + voxelCoordinates: Vec3 + } + orientation: Vec4 + } + zoomFactor: number + } +} + +export type NgConfig = { + showDefaultAnnotations: boolean + layers: Record<string, NgLayerSpec> + gpuMemoryLimit: number +} & NgConfigViewerState + +interface _NehubaConfig { + configName: string + globals: { + hideNullImageValues: boolean + useNehubaLayout: { + keepDefaultLayouts: boolean + } + useNehubaMeshLayer: boolean + rightClickWithCtrlGlobal: boolean + zoomWithoutCtrlGlobal: boolean + useCustomSegmentColors: boolean + } + zoomWithoutCtrl: boolean + hideNeuroglancerUI: boolean + rightClickWithCtrl: boolean + rotateAtViewCentre: boolean + enableMeshLoadingControl: boolean + zoomAtViewCentre: boolean + restrictUserNavigation: boolean + disableSegmentSelection: boolean + dataset: { + imageBackground: Vec4 + initialNgState: NgConfig + } + layout: { + views: string + planarSlicesBackground: Vec4 + useNehubaPerspective: { + enableShiftDrag: boolean + doNotRestrictUserNavigation: boolean + removePerspectiveSlicesBackground: { + mode: string + color: Vec4 + } + perspectiveSlicesBackground: Vec4 + perspectiveBackground: Vec4 + fixedZoomPerspectiveSlices: { + sliceViewportWidth: number + sliceViewportHeight: number + sliceZoom: number + sliceViewportSizeMultiplier: number + } + mesh: { + removeOctant: Vec4 + backFaceColor: Vec3 + removeBasedOnNavigation: boolean + flipRemovedOctant: boolean + surfaceParcellation: boolean + } + centerToOrigin: boolean + drawSubstrates: { + color: Vec4 + } + drawZoomLevels: { + cutOff: number + color: Vec4 + } + restrictZoomLevel: { + minZoom: number + maxZoom: number + } + hideImages: boolean + waitForMesh: boolean + } + } +} + +export type NehubaConfig = RecursivePartial<_NehubaConfig> + +export type NgLayerSpec = { + source: string + transform: number[][] + opacity?: number + visible?: boolean +} + +export type NgPrecompMeshSpec = { + auxMeshes: { + name: string + labelIndicies: number[] + }[] +} & NgLayerSpec + +export type NgSegLayerSpec = { + labelIndicies: number[] +} & NgLayerSpec diff --git a/src/viewerModule/nehuba/config.service/util.spec.ts b/src/viewerModule/nehuba/config.service/util.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7f5727e19755f4ffd0d59540c3ccc93806300fa3 --- /dev/null +++ b/src/viewerModule/nehuba/config.service/util.spec.ts @@ -0,0 +1,141 @@ +import { cvtNavigationObjToNehubaConfig } from './util' + +const currentNavigation = { + position: [4, 5, 6], + orientation: [0, 0, 0, 1], + perspectiveOrientation: [ 0, 0, 0, 1], + perspectiveZoom: 2e5, + zoom: 1e5 +} + +const defaultPerspectiveZoom = 1e6 +const defaultZoom = 1e6 + +const defaultNavigationObject = { + orientation: [0, 0, 0, 1], + perspectiveOrientation: [0 , 0, 0, 1], + perspectiveZoom: defaultPerspectiveZoom, + zoom: defaultZoom, + position: [0, 0, 0], + positionReal: true +} + +const defaultNehubaConfigObject = { + perspectiveOrientation: [0, 0, 0, 1], + perspectiveZoom: 1e6, + navigation: { + pose: { + position: { + voxelCoordinates: [0, 0, 0], + voxelSize: [1,1,1] + }, + orientation: [0, 0, 0, 1], + }, + zoomFactor: defaultZoom + } +} + +const bigbrainNehubaConfig = { + "showDefaultAnnotations": false, + "layers": { + }, + "navigation": { + "pose": { + "position": { + "voxelSize": [ + 21166.666015625, + 20000, + 21166.666015625 + ], + "voxelCoordinates": [ + -21.8844051361084, + 16.288618087768555, + 28.418994903564453 + ] + } + }, + "zoomFactor": 350000 + }, + "perspectiveOrientation": [ + 0.3140767216682434, + -0.7418519854545593, + 0.4988985061645508, + -0.3195493221282959 + ], + "perspectiveZoom": 1922235.5293810747 +} + +describe('> util.ts', () => { + + describe('> cvtNavigationObjToNehubaConfig', () => { + const validNavigationObj = currentNavigation + describe('> if inputs are malformed', () => { + describe('> if navigation object is malformed, uses navigation default object', () => { + it('> if navigation object is null', () => { + const v1 = cvtNavigationObjToNehubaConfig(null, bigbrainNehubaConfig) + const v2 = cvtNavigationObjToNehubaConfig(defaultNavigationObject, bigbrainNehubaConfig) + expect(v1).toEqual(v2) + }) + it('> if navigation object is undefined', () => { + const v1 = cvtNavigationObjToNehubaConfig(undefined, bigbrainNehubaConfig) + const v2 = cvtNavigationObjToNehubaConfig(defaultNavigationObject, bigbrainNehubaConfig) + expect(v1).toEqual(v2) + }) + + it('> if navigation object is otherwise malformed', () => { + const v1 = cvtNavigationObjToNehubaConfig({foo: 'bar'} as any, bigbrainNehubaConfig) + const v2 = cvtNavigationObjToNehubaConfig(defaultNavigationObject, bigbrainNehubaConfig) + expect(v1).toEqual(v2) + + const v3 = cvtNavigationObjToNehubaConfig({} as any, bigbrainNehubaConfig) + const v4 = cvtNavigationObjToNehubaConfig(defaultNavigationObject, bigbrainNehubaConfig) + expect(v3).toEqual(v4) + }) + }) + + describe('> if nehubaConfig object is malformed, use default nehubaConfig obj', () => { + it('> if nehubaConfig is null', () => { + const v1 = cvtNavigationObjToNehubaConfig(validNavigationObj, null) + const v2 = cvtNavigationObjToNehubaConfig(validNavigationObj, defaultNehubaConfigObject) + expect(v1).toEqual(v2) + }) + + it('> if nehubaConfig is undefined', () => { + const v1 = cvtNavigationObjToNehubaConfig(validNavigationObj, undefined) + const v2 = cvtNavigationObjToNehubaConfig(validNavigationObj, defaultNehubaConfigObject) + expect(v1).toEqual(v2) + }) + + it('> if nehubaConfig is otherwise malformed', () => { + const v1 = cvtNavigationObjToNehubaConfig(validNavigationObj, {}) + const v2 = cvtNavigationObjToNehubaConfig(validNavigationObj, defaultNehubaConfigObject) + expect(v1).toEqual(v2) + + const v3 = cvtNavigationObjToNehubaConfig(validNavigationObj, {foo: 'bar'} as any) + const v4 = cvtNavigationObjToNehubaConfig(validNavigationObj, defaultNehubaConfigObject) + expect(v3).toEqual(v4) + }) + }) + }) + it('> converts navigation object and reference nehuba config object to navigation object', () => { + const convertedVal = cvtNavigationObjToNehubaConfig(validNavigationObj, bigbrainNehubaConfig) + const { perspectiveOrientation, orientation, zoom, perspectiveZoom, position } = validNavigationObj + + expect(convertedVal).toEqual({ + navigation: { + pose: { + position: { + voxelSize: bigbrainNehubaConfig.navigation.pose.position.voxelSize, + voxelCoordinates: [0, 1, 2].map(idx => position[idx] / bigbrainNehubaConfig.navigation.pose.position.voxelSize[idx]) + }, + orientation + }, + zoomFactor: zoom + }, + perspectiveOrientation: perspectiveOrientation, + perspectiveZoom: perspectiveZoom + }) + }) + }) + +}) diff --git a/src/viewerModule/nehuba/config.service/util.ts b/src/viewerModule/nehuba/config.service/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..7233d0f26bb2bda42bfe3b0141c9c9b64a377c77 --- /dev/null +++ b/src/viewerModule/nehuba/config.service/util.ts @@ -0,0 +1,485 @@ +import { SapiParcellationModel, SapiSpaceModel, SapiAtlasModel, SapiRegionModel } from 'src/atlasComponents/sapi' +import { SapiVolumeModel, IDS } from 'src/atlasComponents/sapi' +import { atlasSelection } from 'src/state' +import { MultiDimMap } from 'src/util/fn' +import { ParcVolumeSpec } from "../store/util" +import { + NehubaConfig, + NgConfig, + RecursivePartial, + NgLayerSpec, + NgPrecompMeshSpec, + NgSegLayerSpec, +} from "./type" + +// fsaverage uses threesurfer, which, whilst do not use ngId, uses 'left' and 'right' as keys +const fsAverageKeyVal = { + [IDS.PARCELLATION.JBA29]: { + "left hemisphere": "left", + "right hemisphere": "right" + } +} + +/** + * in order to maintain backwards compat with url encoding of selected regions + * TODO setup a sentry to catch if these are ever used. if not, retire the hard coding + */ +const BACKCOMAP_KEY_DICT = { + + // human multi level + 'juelich/iav/atlas/v1.0.0/1': { + // icbm152 + [IDS.TEMPLATES.MNI152]: { + // julich brain v2.6 + 'minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-26': { + 'left hemisphere': 'MNI152_V25_LEFT_NG_SPLIT_HEMISPHERE', + 'right hemisphere': 'MNI152_V25_RIGHT_NG_SPLIT_HEMISPHERE' + }, + // bundle hcp + // even though hcp, long/short bundle, and difumo has no hemisphere distinctions, the way siibra-python parses the region, + // and thus attributes left/right hemisphere, still results in some regions being parsed as left/right hemisphere + "juelich/iav/atlas/v1.0.0/79cbeaa4ee96d5d3dfe2876e9f74b3dc3d3ffb84304fb9b965b1776563a1069c": { + "whole brain": "superficial-white-bundle-HCP", + "left hemisphere": "superficial-white-bundle-HCP", + "right hemisphere": "superficial-white-bundle-HCP" + }, + // julich brain v1.18 + "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579": { + "left hemisphere": "jubrain mni152 v18 left", + "right hemisphere": "jubrain mni152 v18 right", + }, + // long bundle + "juelich/iav/atlas/v1.0.0/5": { + "whole brain": "fibre bundle long", + "left hemisphere": "fibre bundle long", + "right hemisphere": "fibre bundle long", + }, + // bundle short + "juelich/iav/atlas/v1.0.0/6": { + "whole brain": "fibre bundle short", + "left hemisphere": "fibre bundle short", + "right hemisphere": "fibre bundle short", + }, + // difumo 64 + "minds/core/parcellationatlas/v1.0.0/d80fbab2-ce7f-4901-a3a2-3c8ef8a3b721": { + "whole brain": "DiFuMo Atlas (64 dimensions)", + "left hemisphere": "DiFuMo Atlas (64 dimensions)", + "right hemisphere": "DiFuMo Atlas (64 dimensions)", + }, + "minds/core/parcellationatlas/v1.0.0/73f41e04-b7ee-4301-a828-4b298ad05ab8": { + "whole brain": "DiFuMo Atlas (128 dimensions)", + "left hemisphere": "DiFuMo Atlas (128 dimensions)", + "right hemisphere": "DiFuMo Atlas (128 dimensions)", + }, + "minds/core/parcellationatlas/v1.0.0/141d510f-0342-4f94-ace7-c97d5f160235": { + "whole brain": "DiFuMo Atlas (256 dimensions)", + "left hemisphere": "DiFuMo Atlas (256 dimensions)", + "right hemisphere": "DiFuMo Atlas (256 dimensions)", + }, + "minds/core/parcellationatlas/v1.0.0/63b5794f-79a4-4464-8dc1-b32e170f3d16": { + "whole brain": "DiFuMo Atlas (512 dimensions)", + "left hemisphere": "DiFuMo Atlas (512 dimensions)", + "right hemisphere": "DiFuMo Atlas (512 dimensions)", + }, + "minds/core/parcellationatlas/v1.0.0/12fca5c5-b02c-46ce-ab9f-f12babf4c7e1": { + "whole brain": "DiFuMo Atlas (1024 dimensions)", + "left hemisphere": "DiFuMo Atlas (1024 dimensions)", + "right hemisphere": "DiFuMo Atlas (1024 dimensions)", + }, + }, + // colin 27 + "minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992": { + "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-26": { + "left hemisphere": "COLIN_V25_LEFT_NG_SPLIT_HEMISPHERE", + "right hemisphere": "COLIN_V25_RIGHT_NG_SPLIT_HEMISPHERE", + }, + "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579": { + "left hemisphere": "jubrain colin v18 left", + "right hemisphere": "jubrain colin v18 right", + } + }, + // big brain + "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588": { + "minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-26": { + + }, + // isocortex + "juelich/iav/atlas/v1.0.0/4": { + "whole brain": " tissue type: " + }, + // cortical layers + "juelich/iav/atlas/v1.0.0/3": { + "whole brain": "cortical layers" + }, + }, + + // fsaverage + "minds/core/referencespace/v1.0.0/tmp-fsaverage": fsAverageKeyVal, + }, + // allen mouse + 'juelich/iav/atlas/v1.0.0/2': { + // ccf v3 + "minds/core/referencespace/v1.0.0/265d32a0-3d84-40a5-926f-bf89f68212b9": { + // ccf v3 2017 + "minds/core/parcellationatlas/v1.0.0/05655b58-3b6f-49db-b285-64b5a0276f83": { + "whole brain": "v3_2017", + "left hemisphere": "v3_2017", + "right hemisphere": "v3_2017" + }, + // ccf v3 2015, + "minds/core/parcellationatlas/v1.0.0/39a1384b-8413-4d27-af8d-22432225401f": { + "whole brain": "atlas", + "left hemisphere": "atlas", + "right hemisphere": "atlas" + } + } + }, + // waxholm + "minds/core/parcellationatlas/v1.0.0/522b368e-49a3-49fa-88d3-0870a307974a": { + "minds/core/referencespace/v1.0.0/d5717c4a-0fa1-46e6-918c-b8003069ade8": { + // v1.01 + "minds/core/parcellationatlas/v1.0.0/11017b35-7056-4593-baad-3934d211daba": { + "whole brain": "v1_01", + "left hemisphere": "v1_01", + "right hemisphere": "v1_01" + }, + // v2 + "minds/core/parcellationatlas/v1.0.0/2449a7f0-6dd0-4b5a-8f1e-aec0db03679d": { + "whole brain": "v2", + "left hemisphere": "v2", + "right hemisphere": "v2" + }, + // v3 + "minds/core/parcellationatlas/v1.0.0/ebb923ba-b4d5-4b82-8088-fa9215c2e1fe": { + "whole brain": "v3", + "left hemisphere": "v3", + "right hemisphere": "v3" + } + } + } +} + + +export function getTmplNgLayer(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, spaceVolumes: SapiVolumeModel[]): Record<string, NgLayerSpec>{ + if (!atlas || !tmpl) return {} + const ngId = `_${MultiDimMap.GetKey(atlas["@id"], tmpl["@id"], "tmplImage")}` + const tmplImage = spaceVolumes.find(v => "neuroglancer/precomputed" in v.data.detail) + if (!tmplImage) return {} + return { + [ngId]: { + source: `precomputed://${tmplImage.data.url.replace(/^precomputed:\/\//, '')}`, + ...tmplImage.data.detail["neuroglancer/precomputed"] as NgLayerSpec + } + } +} + +export function getTmplAuxNgLayer(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, spaceVolumes: SapiVolumeModel[]): Record<string, NgPrecompMeshSpec>{ + if (!atlas || !tmpl) return {} + const ngId = `_${MultiDimMap.GetKey(atlas["@id"], tmpl["@id"], "auxLayer")}` + const tmplImage = spaceVolumes.find(v => "neuroglancer/precompmesh" in v.data.detail) + if (!tmplImage) return {} + return { + [ngId]: { + source: `precompmesh://${tmplImage.data.url.replace(/^precompmesh:\/\//, '')}`, + ...tmplImage.data.detail["neuroglancer/precompmesh"] as NgPrecompMeshSpec + } + } +} + +export function getParcNgId(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, parc: SapiParcellationModel, region: SapiRegionModel): string { + + let laterality: string = "whole brain" + if (region.name.indexOf("left") >= 0) laterality = "left hemisphere" + if (region.name.indexOf("right") >= 0) laterality = "right hemisphere" + + /** + * for JBA29 in big brain, there exist several volumes. (e.g. v1, v2, v5, interpolated, etc) + */ + if (tmpl['@id'] === IDS.TEMPLATES.BIG_BRAIN && parc['@id'] === IDS.PARCELLATION.JBA29) { + laterality = region.hasAnnotation.visualizedIn?.['@id'] + } + + if (!laterality) { + return null + } + + let ngId = BACKCOMAP_KEY_DICT[atlas["@id"]]?.[tmpl["@id"]]?.[parc["@id"]]?.[laterality] + if (!ngId) { + ngId = `_${MultiDimMap.GetKey(atlas["@id"], tmpl["@id"], parc["@id"], laterality)}` + } + return ngId +} + +const labelIdxRegex = /siibra_python_ng_precomputed_labelindex:\/\/(.*?)#([0-9]+)$/ + +export function getRegionLabelIndex(_atlas: SapiAtlasModel, _tmpl: SapiSpaceModel, _parc: SapiParcellationModel, region: SapiRegionModel): number { + const overwriteLabelIndex = region.hasAnnotation.inspiredBy.map(({ "@id": id }) => labelIdxRegex.exec(id)).filter(v => !!v) + if (overwriteLabelIndex.length > 0) { + const match = overwriteLabelIndex[0] + const volumeId = match[1] + const labelIndex = match[2] + const _labelIndex = Number(labelIndex) + if (!isNaN(_labelIndex)) return _labelIndex + } + const lblIdx = Number(region?.hasAnnotation?.internalIdentifier) + if (isNaN(lblIdx)) return null + return lblIdx +} + +export function getParcNgLayers(atlas: SapiAtlasModel, tmpl: SapiSpaceModel, parc: SapiParcellationModel, parcVolumes: { volume: SapiVolumeModel, volumeMetadata: ParcVolumeSpec }[]): Record<string, NgSegLayerSpec>{ + const returnVal: Record<string, NgSegLayerSpec> = {} + for (const parcVol of parcVolumes) { + if ("spy/volume/neuroglancer/precomputed" !== parcVol.volume['@type']) continue + const { volume, volumeMetadata } = parcVol + const { regions } = volumeMetadata + if (regions.length === 0) { + console.warn(`parc volume with no associated region`) + continue + } + const ngId = getParcNgId(atlas, tmpl, parc, regions[0].region) + + returnVal[ngId] = { + source: `precomputed://${volume.data.url.replace(/^precomputed:\/\//, '')}`, + transform: (volume.data.detail["neuroglancer/precomputed"] as any).transform, + labelIndicies: regions.map(v => v.labelIndex) + } + } + return returnVal +} + +type CongregatedVolume = { + tmplVolumes: SapiVolumeModel[] + tmplAuxMeshVolumes: SapiVolumeModel[] + parcVolumes: { volume: SapiVolumeModel, volumeMetadata: ParcVolumeSpec}[] +} + +export const getNgLayersFromVolumesATP = (volumes: CongregatedVolume, ATP: { atlas: SapiAtlasModel, template: SapiSpaceModel, parcellation: SapiParcellationModel }): { + tmplNgLayers: Record<string, NgLayerSpec> + tmplAuxNgLayers: Record<string, NgPrecompMeshSpec> + parcNgLayers: Record<string, NgSegLayerSpec> +} => { + + const { tmplVolumes, tmplAuxMeshVolumes, parcVolumes } = volumes + const { atlas, template, parcellation } = ATP + return { + tmplNgLayers: getTmplNgLayer(atlas, template, tmplVolumes), + tmplAuxNgLayers: getTmplAuxNgLayer(atlas, template, tmplAuxMeshVolumes), + parcNgLayers: getParcNgLayers(atlas, template, parcellation, parcVolumes) + } +} + +export const defaultNehubaConfig: NehubaConfig = { + "configName": "", + "globals": { + "hideNullImageValues": true, + "useNehubaLayout": { + "keepDefaultLayouts": false + }, + "useNehubaMeshLayer": true, + "rightClickWithCtrlGlobal": false, + "zoomWithoutCtrlGlobal": false, + "useCustomSegmentColors": true + }, + "zoomWithoutCtrl": true, + "hideNeuroglancerUI": true, + "rightClickWithCtrl": true, + "rotateAtViewCentre": true, + "enableMeshLoadingControl": true, + "zoomAtViewCentre": true, + "restrictUserNavigation": true, + "disableSegmentSelection": false, + "dataset": { + "imageBackground": [ + 1, + 1, + 1, + 1 + ], + "initialNgState": { + "showDefaultAnnotations": false, + "layers": {}, + } + }, + "layout": { + "views": "hbp-neuro", + "planarSlicesBackground": [ + 1, + 1, + 1, + 1 + ], + "useNehubaPerspective": { + "enableShiftDrag": false, + "doNotRestrictUserNavigation": false, + "perspectiveSlicesBackground": [ + 1, + 1, + 1, + 1 + ], + "perspectiveBackground": [ + 1, + 1, + 1, + 1 + ], + "mesh": { + "backFaceColor": [ + 1, + 1, + 1, + 1 + ], + "removeBasedOnNavigation": true, + "flipRemovedOctant": true + }, + "hideImages": false, + "waitForMesh": false, + } + } +} + +export const spaceMiscInfoMap = new Map([ + [IDS.TEMPLATES.BIG_BRAIN, { + name: 'bigbrain', + scale: 1, + }], + [IDS.TEMPLATES.MNI152, { + name: 'icbm2009c', + scale: 1, + }], + ['minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992', { + name: 'colin27', + scale: 1, + }], + ['minds/core/referencespace/v1.0.0/265d32a0-3d84-40a5-926f-bf89f68212b9', { + name: 'allen-mouse', + scale: 0.1, + }], + ['minds/core/referencespace/v1.0.0/d5717c4a-0fa1-46e6-918c-b8003069ade8', { + name: 'waxholm', + scale: 0.1, + }], +]) + +export function getNehubaConfig(space: SapiSpaceModel): NehubaConfig { + + const darkTheme = space["@id"] !== "minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588" + const { scale } = spaceMiscInfoMap.get(space["@id"]) || { scale: 1 } + const backgrd = darkTheme + ? [0,0,0,1] + : [1,1,1,1] + + const rmPsp = darkTheme + ? {"mode":"<","color":[0.1,0.1,0.1,1]} + :{"color":[1,1,1,1],"mode":"=="} + const drawSubstrates = darkTheme + ? {"color":[0.5,0.5,1,0.2]} + : {"color":[0,0,0.5,0.15]} + const drawZoomLevels = darkTheme + ? {"cutOff":150000 * scale } + : {"cutOff":200000 * scale,"color":[0.5,0,0,0.15] } + + // enable surface parcellation + // otherwise, on segmentation selection, the unselected meshes will also be invisible + const surfaceParcellation = space["@id"] === 'minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992' + return { + "configName": "", + "globals": { + "hideNullImageValues": true, + "useNehubaLayout": { + "keepDefaultLayouts": false + }, + "useNehubaMeshLayer": true, + "rightClickWithCtrlGlobal": false, + "zoomWithoutCtrlGlobal": false, + "useCustomSegmentColors": true + }, + "zoomWithoutCtrl": true, + "hideNeuroglancerUI": true, + "rightClickWithCtrl": true, + "rotateAtViewCentre": true, + "enableMeshLoadingControl": true, + "zoomAtViewCentre": true, + // "restrictUserNavigation": true, + "dataset": { + "imageBackground": backgrd, + "initialNgState": { + "showDefaultAnnotations": false, + "layers": {}, + "navigation": { + "zoomFactor": 350000 * scale, + }, + "perspectiveOrientation": [ + 0.3140767216682434, + -0.7418519854545593, + 0.4988985061645508, + -0.3195493221282959 + ], + "perspectiveZoom": 1922235.5293810747 * scale + } + }, + "layout": { + "useNehubaPerspective": { + "perspectiveSlicesBackground": backgrd, + "removePerspectiveSlicesBackground": rmPsp, + "perspectiveBackground": backgrd, + "fixedZoomPerspectiveSlices": { + "sliceViewportWidth": 300, + "sliceViewportHeight": 300, + "sliceZoom": 563818.3562426177 * scale, + "sliceViewportSizeMultiplier": 2 + }, + "mesh": { + "backFaceColor": backgrd, + "removeBasedOnNavigation": true, + "flipRemovedOctant": true, + surfaceParcellation + }, + "centerToOrigin": true, + "drawSubstrates": drawSubstrates, + "drawZoomLevels": drawZoomLevels, + "restrictZoomLevel": { + "minZoom": 1200000 * scale, + "maxZoom": 3500000 * scale + } + } + } + } +} + + +export function cvtNavigationObjToNehubaConfig(navigationObj: atlasSelection.AtlasSelectionState['navigation'], nehubaConfigObj: RecursivePartial<NgConfig>): RecursivePartial<NgConfig>{ + const { + orientation = [0, 0, 0, 1], + perspectiveOrientation = [0, 0, 0, 1], + perspectiveZoom = 1e6, + zoom = 1e6, + position = [0, 0, 0], + } = navigationObj || {} + + const voxelSize = (() => { + const { + navigation = {} + } = nehubaConfigObj || {} + const { pose = {} } = navigation + const { position = {} } = pose + const { voxelSize = [1, 1, 1] } = position + return voxelSize + })() + + return { + perspectiveOrientation, + perspectiveZoom, + navigation: { + pose: { + position: { + voxelCoordinates: [0, 1, 2].map(idx => position[idx] / voxelSize[idx]), + voxelSize + }, + orientation, + }, + zoomFactor: zoom + } + } +} diff --git a/src/viewerModule/nehuba/constants.ts b/src/viewerModule/nehuba/constants.ts index bbd36606525015fcd2f8e6315a061ea2a7f56a36..ca5ae4be810af998f93318a6348f751555fabff3 100644 --- a/src/viewerModule/nehuba/constants.ts +++ b/src/viewerModule/nehuba/constants.ts @@ -4,17 +4,6 @@ import { Observable } from 'rxjs' export { getNgIds } from 'src/util/fn' export const NEHUBA_VIEWER_FEATURE_KEY = 'ngViewerFeature' -export interface INgLayerInterface { - name: string // displayName - source: string - mixability: string // base | mixable | nonmixable - annotation?: string // - id?: string // unique identifier - visible?: boolean - shader?: string - transform?: any -} - export interface IRegion { [key: string]: any ngId: string @@ -75,3 +64,5 @@ export interface IMeshesToLoad { } export const SET_MESHES_TO_LOAD = new InjectionToken<Observable<IMeshesToLoad>>('SET_MESHES_TO_LOAD') + +export const PMAP_LAYER_NAME = 'regional-pmap' diff --git a/src/viewerModule/nehuba/layerCtrl.service/index.ts b/src/viewerModule/nehuba/layerCtrl.service/index.ts index 05cbb34fe0a5803c935749c2c45e6345bb467746..09065a46f416dd4373e98395f4cf985715718cce 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/index.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/index.ts @@ -7,5 +7,5 @@ export { SET_COLORMAP_OBS, SET_LAYER_VISIBILITY, getRgb, - INgLayerInterface, + } from './layerCtrl.util' diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..579f7fb79ab3e65547cc420391f2097587eeff18 --- /dev/null +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.effects.ts @@ -0,0 +1,328 @@ +import { Injectable } from "@angular/core"; +import { createEffect } from "@ngrx/effects"; +import { select, Store } from "@ngrx/store"; +import { forkJoin, of } from "rxjs"; +import { mapTo, switchMap, withLatestFrom, filter, catchError, map, debounceTime, shareReplay, distinctUntilChanged, startWith, pairwise } from "rxjs/operators"; +import { SAPI, SapiAtlasModel, SapiFeatureModel, SapiParcellationModel, SapiSpaceModel, SapiRegionModel } from "src/atlasComponents/sapi"; +import { SapiVOIDataResponse, SapiVolumeModel } from "src/atlasComponents/sapi/type"; +import { atlasAppearance, atlasSelection, userInteraction } from "src/state"; +import { arrayEqual } from "src/util/array"; +import { EnumColorMapName } from "src/util/colorMaps"; +import { getShader } from "src/util/constants"; +import { getNgLayersFromVolumesATP, getRegionLabelIndex } from "../config.service"; +import { ParcVolumeSpec } from "../store/util"; +import { PMAP_LAYER_NAME } from "../constants"; + +@Injectable() +export class LayerCtrlEffects { + onRegionSelectClearPmapLayer = createEffect(() => this.store.pipe( + select(atlasSelection.selectors.selectedRegions), + distinctUntilChanged( + arrayEqual((o, n) => o["@id"] === n["@id"]) + ), + mapTo( + atlasAppearance.actions.removeCustomLayer({ + id: PMAP_LAYER_NAME + }) + ) + )) + + onRegionSelectShowNewPmapLayer = createEffect(() => this.store.pipe( + select(atlasSelection.selectors.selectedRegions), + filter(regions => regions.length > 0), + withLatestFrom( + this.store.pipe( + atlasSelection.fromRootStore.distinctATP() + ) + ), + switchMap(([ regions, { atlas, parcellation, template } ]) => { + const sapiRegion = this.sapi.getRegion(atlas["@id"], parcellation["@id"], regions[0].name) + return sapiRegion.getMapInfo(template["@id"]).pipe( + map(val => + atlasAppearance.actions.addCustomLayer({ + customLayer: { + clType: "customlayer/nglayer", + id: PMAP_LAYER_NAME, + source: `nifti://${sapiRegion.getMapUrl(template["@id"])}`, + shader: getShader({ + colormap: EnumColorMapName.VIRIDIS, + highThreshold: val.max, + lowThreshold: val.min, + removeBg: true, + }) + } + }) + ), + catchError(() => of( + atlasAppearance.actions.removeCustomLayer({ + id: PMAP_LAYER_NAME + }) + )) + ) + }), + )) + + onATP$ = this.store.pipe( + atlasSelection.fromRootStore.distinctATP(), + map(val => val as { atlas: SapiAtlasModel, parcellation: SapiParcellationModel, template: SapiSpaceModel }) + ) + + onShownFeature = createEffect(() => this.store.pipe( + select(userInteraction.selectors.selectedFeature), + startWith(null as SapiFeatureModel), + pairwise(), + map(([ prev, curr ]) => { + const removeLayers: atlasAppearance.NgLayerCustomLayer[] = [] + const addLayers: atlasAppearance.NgLayerCustomLayer[] = [] + if (prev?.["@type"] === "siibra/features/voi") { + removeLayers.push( + ...(prev as SapiVOIDataResponse).volumes.map(v => { + return { + id: v.metadata.fullName, + clType: "customlayer/nglayer", + source: v.data.url, + transform: v.data.detail['neuroglancer/precomputed']['transform'], + opacity: 1.0, + visible: true, + shader: v.data.detail['neuroglancer/precomputed']['shader'] || getShader() + } as atlasAppearance.NgLayerCustomLayer + }) + ) + } + if (curr?.["@type"] === "siibra/features/voi") { + addLayers.push( + ...(curr as SapiVOIDataResponse).volumes.map(v => { + return { + id: v.metadata.fullName, + clType: "customlayer/nglayer", + source: `precomputed://${v.data.url}`, + transform: v.data.detail['neuroglancer/precomputed']['transform'], + opacity: v.data.detail['neuroglancer/precomputed']['opacity'] || 1.0, + visible: true, + shader: v.data.detail['neuroglancer/precomputed']['shader'] || getShader() + } as atlasAppearance.NgLayerCustomLayer + }) + ) + } + return { removeLayers, addLayers } + }), + filter(({ removeLayers, addLayers }) => removeLayers.length !== 0 || addLayers.length !== 0), + switchMap(({ removeLayers, addLayers }) => of(...[ + ...removeLayers.map( + l => atlasAppearance.actions.removeCustomLayer({ id: l.id }) + ), + ...addLayers.map( + l => atlasAppearance.actions.addCustomLayer({ customLayer: l }) + ) + ])) + )) + + onATPClearBaseLayers = createEffect(() => this.onATP$.pipe( + withLatestFrom( + this.store.pipe( + select(atlasAppearance.selectors.customLayers), + map( + cl => cl.filter(layer => + layer.clType === "baselayer/nglayer" + || layer.clType === "customlayer/nglayer" + ) + ) + ) + ), + switchMap(([_, layers]) => + of( + ...layers.map(layer => + atlasAppearance.actions.removeCustomLayer({ + id: layer.id + }) + ) + ) + ) + )) + + private getNgLayers(atlas: SapiAtlasModel, parcellation: SapiParcellationModel, template: SapiSpaceModel){ + + if (!!parcellation && !template) { + throw new Error(`parcellation defined, but template not defined!`) + } + + /** + * some labelled maps (such as julich brain in big brain) do not have the volumes defined on the parcellation level. + * While we have the URLs of these volumes (the method we use is also kind of hacky), and in theory, we could construct a volume object directly + * It is probably better to fetch the correct volume object to begin with + */ + const parcVolumes$ = !parcellation + ? of([] as {volume: SapiVolumeModel, volumeMetadata: ParcVolumeSpec}[]) + : forkJoin([ + this.sapi.getParcellation(atlas["@id"], parcellation["@id"]).getRegions(template["@id"]).pipe( + map(regions => { + + const volumeIdToRegionMap = new Map<string, { + labelIndex: number + region: SapiRegionModel + }[]>() + + for (const r of regions) { + const volumeId = r?.hasAnnotation?.visualizedIn?.["@id"] + if (!volumeId) continue + + const labelIndex = getRegionLabelIndex(atlas, template, parcellation, r) + if (!labelIndex) continue + + if (!volumeIdToRegionMap.has(volumeId)) { + volumeIdToRegionMap.set(volumeId, []) + } + volumeIdToRegionMap.get(volumeId).push({ + labelIndex, + region: r + }) + } + return volumeIdToRegionMap + }) + ), + this.sapi.getParcellation(atlas["@id"], parcellation["@id"]).getVolumes() + ]).pipe( + switchMap(([ volumeIdToRegionMap, volumes ]) => { + const missingVolumeIds = Array.from(volumeIdToRegionMap.keys()).filter(id => volumes.map(v => v["@id"]).indexOf(id) < 0) + + const volumesFromParc: {volume: SapiVolumeModel, volumeMetadata: ParcVolumeSpec}[] = volumes.map( + volume => { + const found = volumeIdToRegionMap.get(volume["@id"]) + if (!found) return null + + try { + + const volumeMetadata: ParcVolumeSpec = { + regions: found, + parcellation, + volumeSrc: volume.data.url + } + return { + volume, + volumeMetadata, + } + } catch (e) { + console.error(e) + return null + } + } + ).filter(v => v?.volumeMetadata?.regions) + + if (missingVolumeIds.length === 0) return of([...volumesFromParc]) + return forkJoin( + missingVolumeIds.map(missingId => { + if (!volumeIdToRegionMap.has(missingId)) { + console.warn(`volumeIdToRegionMap does not have volume with id ${missingId}`) + return of(null as SapiVolumeModel) + } + const { region } = volumeIdToRegionMap.get(missingId)[0] + return this.sapi.getRegion(atlas["@id"], parcellation["@id"], region.name).getVolumeInstance(missingId).pipe( + catchError((err, obs) => of(null as SapiVolumeModel)) + ) + }) + ).pipe( + map((missingVolumes: SapiVolumeModel[]) => { + + const volumesFromRegion: { volume: SapiVolumeModel, volumeMetadata: ParcVolumeSpec }[] = missingVolumes.map( + volume => { + if (!volume || !volumeIdToRegionMap.has(volume['@id'])) { + return null + } + + try { + + const found = volumeIdToRegionMap.get(volume['@id']) + const volumeMetadata: ParcVolumeSpec = { + regions: found, + parcellation, + volumeSrc: volume.data.url + } + return { + volume, + volumeMetadata + } + } catch (e) { + console.error(`volume from region error`, e) + return null + } + } + ).filter( + v => !!v + ) + + return [ + ...volumesFromParc, + ...volumesFromRegion + ] + }) + ) + }) + ) + + const spaceVols$ = !!template + ? this.sapi.getSpace(atlas["@id"], template["@id"]).getVolumes().pipe( + shareReplay(1), + ) + : of([] as SapiVolumeModel[]) + + return forkJoin({ + tmplVolumes: spaceVols$.pipe( + map( + volumes => volumes.filter(vol => "neuroglancer/precomputed" in vol.data.detail) + ), + ), + tmplAuxMeshVolumes: spaceVols$.pipe( + map( + volumes => volumes.filter(vol => "neuroglancer/precompmesh" in vol.data.detail) + ), + ), + parcVolumes: parcVolumes$.pipe( + ) + }) + } + + onATPDebounceNgLayers$ = this.onATP$.pipe( + debounceTime(16), + switchMap(({ atlas, parcellation, template }) => + this.getNgLayers(atlas, parcellation, template).pipe( + map(volumes => getNgLayersFromVolumesATP(volumes, { atlas, parcellation, template })) + ) + ), + shareReplay(1) + ) + + onATPDebounceAddBaseLayers$ = createEffect(() => this.onATPDebounceNgLayers$.pipe( + switchMap(ngLayers => { + const { parcNgLayers, tmplAuxNgLayers, tmplNgLayers } = ngLayers + + const customBaseLayers: atlasAppearance.NgLayerCustomLayer[] = [] + for (const layers of [parcNgLayers, tmplAuxNgLayers, tmplNgLayers]) { + for (const key in layers) { + const { source, transform, opacity, visible } = layers[key] + customBaseLayers.push({ + clType: "baselayer/nglayer", + id: key, + source, + transform, + opacity, + visible, + }) + } + } + return of( + ...customBaseLayers.map(layer => + atlasAppearance.actions.addCustomLayer({ + customLayer: layer + }) + ) + ) + }) + )) + + constructor( + private store: Store<any>, + private sapi: SAPI, + ){ + + } +} \ No newline at end of file diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts index 45bbe33795462fcb6669da903c56063dc76f11d9..23ad93defa44bb856c658d5b5e850acf9961895d 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.spec.ts @@ -1,14 +1,15 @@ -import { fakeAsync, TestBed, tick } from "@angular/core/testing" +import { fakeAsync, TestBed } from "@angular/core/testing" import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { viewerStateCustomLandmarkSelector, viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors" import { NehubaLayerControlService } from "./layerCtrl.service" import * as layerCtrlUtil from '../constants' -import { hot } from "jasmine-marbles" -import { IColorMap } from "./layerCtrl.util" -import { debounceTime } from "rxjs/operators" -import { ngViewerSelectorClearView, ngViewerSelectorLayers } from "src/services/state/ngViewerState.store.helper" -import { RouterService } from "src/routerModule/router.service" +import { + annotation, + atlasAppearance, + atlasSelection +} from "src/state" +import { LayerCtrlEffects } from "./layerCtrl.effects" import { NEVER } from "rxjs" +import { RouterService } from "src/routerModule/router.service" describe('> layerctrl.service.ts', () => { describe('> NehubaLayerControlService', () => { @@ -26,6 +27,12 @@ describe('> layerctrl.service.ts', () => { }, NehubaLayerControlService, provideMockStore(), + { + provide: LayerCtrlEffects, + useValue: { + onATPDebounceNgLayers$: NEVER + } + } ] }) @@ -35,10 +42,12 @@ describe('> layerctrl.service.ts', () => { layerCtrlUtil, 'getMultiNgIdsRegionsLabelIndexMap' ).and.returnValue(() => getMultiNgIdsRegionsLabelIndexMapReturnVal) - mockStore.overrideSelector(viewerStateCustomLandmarkSelector, []) - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, []) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, {}) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {}) + mockStore.overrideSelector(atlasAppearance.selectors.customLayers, []) + mockStore.overrideSelector(atlasAppearance.selectors.showDelineation, true) + mockStore.overrideSelector(annotation.selectors.annotations, []) + mockStore.overrideSelector(atlasSelection.selectors.selectedRegions, []) + mockStore.overrideSelector(atlasSelection.selectors.selectedTemplate, {} as any) + mockStore.overrideSelector(atlasSelection.selectors.selectedParcellation, {} as any) }) it('> can be init', () => { @@ -51,132 +60,27 @@ describe('> layerctrl.service.ts', () => { describe('> template/parc has no aux meshes', () => { it('> calls getMultiNgIdsRegionsLabelIndexMapReturn', () => { - const service = TestBed.inject(NehubaLayerControlService) - service.setColorMap$.subscribe() - expect(getMultiNgIdsRegionsLabelIndexMapSpy).toHaveBeenCalled() + }) it('> emitted value is as expected', fakeAsync(() => { - const map = new Map<number, layerCtrlUtil.IRegion>() - getMultiNgIdsRegionsLabelIndexMapReturnVal.set( - 'foo-bar', - map - ) - map.set(1, { - ngId: 'foo-bar', - rgb: [100, 200, 255] - }) - map.set(2, { - ngId: 'foo-bar', - rgb: [15, 15, 15] - }) - - const service = TestBed.inject(NehubaLayerControlService) - let v: any - service.setColorMap$.subscribe(val => { - v = val - }) - tick(32) - const expectedVal = { - 'foo-bar': { - 1: { red: 100, green: 200, blue: 255 }, - 2: { red: 15, green: 15, blue: 15} - } - } - expect(v).toEqual(expectedVal) + })) }) describe('> template/parc has aux meshes', () => { - let tmplAuxMeshes = [{ - name: 'foo-bar', - ngId: 'bazz', - labelIndicies: [1,2,3], - rgb: [100, 100, 100] - }, { - name: 'hello-world', - ngId: 'hello-world', - labelIndicies: [4,5,6], - rgb: [200, 200, 200] - }] - let parcAuxMeshes = [{ - name: 'hello-world', - ngId: 'hello-world', - labelIndicies: [10,11,12], - rgb: [255, 255, 255] - }] + beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, { - auxMeshes: tmplAuxMeshes - }) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, { - auxMeshes: parcAuxMeshes - }) + }) it('> should inherit values from tmpl and parc', fakeAsync(() => { - const service = TestBed.inject(NehubaLayerControlService) - let val - service.setColorMap$.subscribe(v => { - val = v - }) - - tick(32) - - expect(val).toEqual({ - 'bazz': { - 1: { red: 100, green: 100, blue: 100 }, - 2: { red: 100, green: 100, blue: 100 }, - 3: { red: 100, green: 100, blue: 100 }, - }, - 'hello-world': { - 4: { red: 200, green: 200, blue: 200 }, - 5: { red: 200, green: 200, blue: 200 }, - 6: { red: 200, green: 200, blue: 200 }, - 10: { red: 255, green: 255, blue: 255 }, - 11: { red: 255, green: 255, blue: 255 }, - 12: { red: 255, green: 255, blue: 255 }, - } - }) })) it('> should overwrite any value if at all, from region', fakeAsync(() => { - const map = new Map<number, layerCtrlUtil.IRegion>() - map.set(10, { - ngId: 'hello-world', - rgb: [0, 0, 0] - }) - map.set(15, { - ngId: 'hello-world', - rgb: [0, 0, 0] - }) - getMultiNgIdsRegionsLabelIndexMapReturnVal.set('hello-world', map) - - const service = TestBed.inject(NehubaLayerControlService) - let val - service.setColorMap$.subscribe(v => { - val = v - }) - - tick(32) - expect(val).toEqual({ - 'bazz': { - 1: { red: 100, green: 100, blue: 100 }, - 2: { red: 100, green: 100, blue: 100 }, - 3: { red: 100, green: 100, blue: 100 }, - }, - 'hello-world': { - 4: { red: 200, green: 200, blue: 200 }, - 5: { red: 200, green: 200, blue: 200 }, - 6: { red: 200, green: 200, blue: 200 }, - 10: { red: 255, green: 255, blue: 255 }, - 11: { red: 255, green: 255, blue: 255 }, - 12: { red: 255, green: 255, blue: 255 }, - 15: { red: 0, green: 0, blue: 0 }, - } - }) + })) }) }) @@ -195,66 +99,13 @@ describe('> layerctrl.service.ts', () => { describe('> overwriteColorMap$ firing', () => { beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, {}) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {}) - const map = new Map<number, layerCtrlUtil.IRegion>() - getMultiNgIdsRegionsLabelIndexMapReturnVal.set( - 'foo-bar', - map - ) - map.set(1, { - ngId: 'foo-bar', - rgb: [100, 200, 255] - }) - map.set(2, { - ngId: 'foo-bar', - rgb: [15, 15, 15] - }) }) it('> should overwrite existing colormap', () => { - const service = TestBed.inject(NehubaLayerControlService) - service.overwriteColorMap$.next(foobar2) - - expect(service.setColorMap$).toBeObservable( - hot('(b)', { - a: foobar1, - b: foobar2 - }) - ) + }) it('> unsub/resub should not result in overwritecolormap last emitted value', fakeAsync(() => { - const service = TestBed.inject(NehubaLayerControlService) - - let subscrbiedVal: IColorMap - const sub = service.setColorMap$.pipe( - debounceTime(16), - ).subscribe(val => { - subscrbiedVal = val - }) - - // see TODO this is a dirty fix - tick(32) - service.overwriteColorMap$.next(foobar2) - tick(32) - expect(subscrbiedVal).toEqual(foobar2) - tick(16) - sub.unsubscribe() - subscrbiedVal = null - - // mock emit selectParc etc... - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {}) - mockStore.setState({}) - const sub2 = service.setColorMap$.pipe( - debounceTime(16), - ).subscribe(val => { - subscrbiedVal = val - }) - - tick(32) - expect(subscrbiedVal).toEqual(foobar1) - sub2.unsubscribe() })) }) @@ -263,48 +114,10 @@ describe('> layerctrl.service.ts', () => { describe('> visibleLayer$', () => { beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, { - ngId: 'tmplNgId', - auxMeshes: [{ - ngId: 'tmplAuxId1', - labelIndicies: [1,2,3] - },{ - ngId: 'tmplAuxId2', - labelIndicies: [1,2,3] - }] - }) - - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, { - auxMeshes: [{ - ngId: 'parcAuxId1', - labelIndicies: [1,2,3], - },{ - ngId: 'parcAuxId2', - labelIndicies: [1,2,3] - }] - }) - - getMultiNgIdsRegionsLabelIndexMapReturnVal.set( - 'regionsNgId1', null - ) - getMultiNgIdsRegionsLabelIndexMapReturnVal.set( - 'regionsNgId2', null - ) }) it('> combines ngId of template, aux mesh and regions', () => { - const service = TestBed.inject(NehubaLayerControlService) - expect(service.visibleLayer$).toBeObservable(hot('a', { - a: [ - 'tmplNgId', - 'tmplAuxId1', - 'tmplAuxId2', - 'parcAuxId1', - 'parcAuxId2', - 'regionsNgId1', - 'regionsNgId2', - ] - })) + }) }) @@ -318,106 +131,48 @@ describe('> layerctrl.service.ts', () => { labelIndex: 2 } beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, []) - mockStore.overrideSelector(ngViewerSelectorLayers, []) - mockStore.overrideSelector(ngViewerSelectorClearView, false) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {}) }) it('> by default, should return []', () => { - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: [] - }) - ) + }) describe('> if sel regions exist', () => { beforeEach(() => { - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [ - region1, region2 - ]) + }) it('> default, should return encoded strings', () => { - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [ - region1, region2 - ]) - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: [`ngid#1`, `ngid#2`] - }) - ) + }) it('> if clearflag is true, then return []', () => { - mockStore.overrideSelector(ngViewerSelectorClearView, true) - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: [] - }) - ) }) }) describe('> if non mixable layer exist', () => { beforeEach(() => { - mockStore.overrideSelector(ngViewerSelectorLayers, [{ - mixability: 'nonmixable' - }]) }) it('> default, should return null', () => { - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: null - }) - ) + }) it('> if regions selected, should still return null', () => { - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [ - region1, region2 - ]) - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: null - }) - ) }) describe('> if clear flag is set', () => { beforeEach(() => { - mockStore.overrideSelector(ngViewerSelectorClearView, true) + }) it('> default, should return []', () => { - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: [] - }) - ) }) it('> if reg selected, should return []', () => { - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [ - region1, region2 - ]) - const service = TestBed.inject(NehubaLayerControlService) - expect(service.segmentVis$).toBeObservable( - hot('a', { - a: [] - }) - ) }) }) }) diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts index 3c0bbbeb52c0eb12e1e989c8ad685db4d9bbf92e..136c0d0a314394628ddccdcd91010e85420f7524 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.service.ts @@ -1,19 +1,21 @@ -import { Inject, Injectable, OnDestroy, Optional } from "@angular/core"; +import { Injectable, OnDestroy } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { BehaviorSubject, combineLatest, from, merge, NEVER, Observable, of, Subject, Subscription } from "rxjs"; -import { debounceTime, distinctUntilChanged, filter, map, mapTo, shareReplay, startWith, switchMap, withLatestFrom } from "rxjs/operators"; -import { viewerStateCustomLandmarkSelector, viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors"; -import { getRgb, IColorMap, INgLayerCtrl, INgLayerInterface, TNgLayerCtrl } from "./layerCtrl.util"; -import { getMultiNgIdsRegionsLabelIndexMap } from "../constants"; -import { IAuxMesh } from '../store' -import { REGION_OF_INTEREST } from "src/util/interfaces"; -import { TRegionDetail } from "src/util/siibraApiConstants/types"; -import { EnumColorMapName } from "src/util/colorMaps"; -import { getShader, PMAP_DEFAULT_CONFIG } from "src/util/constants"; -import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerSelectorClearView, ngViewerSelectorLayers } from "src/services/state/ngViewerState.store.helper"; -import { serialiseParcellationRegion } from 'common/util' -import { _PLI_VOLUME_INJ_TOKEN, _TPLIVal } from "src/glue"; -import { RouterService } from "src/routerModule/router.service"; +import { combineLatest, merge, Observable, Subject, Subscription } from "rxjs"; +import { debounceTime, distinctUntilChanged, filter, map, pairwise, shareReplay, startWith, switchMap, withLatestFrom } from "rxjs/operators"; +import { IColorMap, INgLayerCtrl, TNgLayerCtrl } from "./layerCtrl.util"; +import { SAPIRegion } from "src/atlasComponents/sapi/core"; +import { getParcNgId } from "../config.service" +import { getRegionLabelIndex } from "../config.service/util"; +import { annotation, atlasAppearance, atlasSelection } from "src/state"; +import { serializeSegment } from "../util"; +import { LayerCtrlEffects } from "./layerCtrl.effects"; +import { arrayEqual } from "src/util/array"; +import { ColorMapCustomLayer } from "src/state/atlasAppearance"; +import { SapiRegionModel } from "src/atlasComponents/sapi"; +import { AnnotationLayer } from "src/atlasComponents/annotations"; +import { PMAP_LAYER_NAME } from "../constants" +import { EnumColorMapName, mapKeyColorMap } from "src/util/colorMaps"; +import { getShader } from "src/util/constants"; export const BACKUP_COLOR = { red: 255, @@ -21,241 +23,152 @@ export const BACKUP_COLOR = { blue: 255 } -export function getAuxMeshesAndReturnIColor(auxMeshes: IAuxMesh[]): IColorMap{ - const returnVal: IColorMap = {} - for (const auxMesh of auxMeshes as IAuxMesh[]) { - const { ngId, labelIndicies, rgb = [255, 255, 255] } = auxMesh - const auxMeshColorMap = returnVal[ngId] || {} - for (const lblIdx of labelIndicies) { - auxMeshColorMap[lblIdx as number] = { - red: rgb[0] as number, - green: rgb[1] as number, - blue: rgb[2] as number, - } - } - returnVal[ngId] = auxMeshColorMap - } - return returnVal -} - @Injectable({ providedIn: 'root' }) export class NehubaLayerControlService implements OnDestroy{ - static PMAP_LAYER_NAME = 'regional-pmap' - private selectedRegion$ = this.store$.pipe( - select(viewerStateSelectedRegionsSelector), + select(atlasSelection.selectors.selectedRegions), shareReplay(1), ) - private selectedParcellation$ = this.store$.pipe( - select(viewerStateSelectedParcellationSelector) + + private defaultNgLayers$ = this.layerEffects.onATPDebounceNgLayers$ + + private selectedATP$ = this.store$.pipe( + atlasSelection.fromRootStore.distinctATP(), + shareReplay(1), ) - private selectedTemplateSelector$ = this.store$.pipe( - select(viewerStateSelectedTemplateSelector) + public selectedATPR$ = this.selectedATP$.pipe( + switchMap(({ atlas, template, parcellation }) => + this.store$.pipe( + select(atlasSelection.selectors.selectedParcAllRegions), + map(regions => ({ + atlas, template, parcellation, regions + })), + shareReplay(1) + ) + ) ) - private selParcNgIdMap$ = this.selectedParcellation$.pipe( - map(parc => getMultiNgIdsRegionsLabelIndexMap(parc)), - shareReplay(1), + private customLayers$ = this.store$.pipe( + select(atlasAppearance.selectors.customLayers), + distinctUntilChanged(arrayEqual((o, n) => o.id === n.id)), + shareReplay(1) ) - - private activeColorMap$: Observable<IColorMap> = combineLatest([ - this.selParcNgIdMap$.pipe( - map(map => { + private activeColorMap$ = combineLatest([ + combineLatest([ + this.selectedATPR$, + this.customLayers$, + ]).pipe( + map(([{ atlas, parcellation, regions, template }, layers]) => { const returnVal: IColorMap = {} - for (const [ key, val ] of map.entries()) { - returnVal[key] = {} - for (const [ lblIdx, region ] of val.entries()) { - const rgb = getRgb(lblIdx, region) - returnVal[key][lblIdx] = rgb + + const cmCustomLayers = layers.filter(l => l.clType === "customlayer/colormap") as ColorMapCustomLayer[] + const cmBaseLayers = layers.filter(l => l.clType === "baselayer/colormap") as ColorMapCustomLayer[] + + const useCm = (() => { + /** + * if custom layer exist, use the last custom layer + */ + if (cmCustomLayers.length > 0) return cmCustomLayers[cmCustomLayers.length - 1].colormap + /** + * otherwise, use last baselayer + */ + if (cmBaseLayers.length > 0) return cmBaseLayers[cmBaseLayers.length - 1].colormap + /** + * fallback color map + */ + return { + set: () => { + throw new Error(`cannot set`) + }, + get: (r: SapiRegionModel) => SAPIRegion.GetDisplayColor(r) } + })() + + for (const r of regions) { + + if (!r.hasAnnotation) continue + if (!r.hasAnnotation.visualizedIn) continue + + const ngId = getParcNgId(atlas, template, parcellation, r) + const labelIndex = getRegionLabelIndex(atlas, template, parcellation, r) + if (!labelIndex) continue + + const [ red, green, blue ] = useCm.get(r) + + if (!returnVal[ngId]) { + returnVal[ngId] = {} + } + returnVal[ngId][labelIndex] = { red, green, blue } } return returnVal }) ), - this.selectedRegion$, - this.selectedTemplateSelector$.pipe( - map(template => { - const { auxMeshes = [] } = template || {} - return getAuxMeshesAndReturnIColor(auxMeshes) - }) - ), - this.selectedParcellation$.pipe( - map(parc => { - const { auxMeshes = [] } = parc || {} - return getAuxMeshesAndReturnIColor(auxMeshes) - }) - ), - ]).pipe( - map(([ regions, selReg, ...auxMeshesArr ]) => { - - const returnVal: IColorMap = {} - if (selReg.length === 0) { - for (const key in regions) { - returnVal[key] = regions[key] - } - } else { - /** - * if selected regions are non empty - * set the selected regions to show color, - * but the rest to show white - */ - for (const key in regions) { - const colorMap = {} - returnVal[key] = colorMap - for (const lblIdx in regions[key]) { - if (selReg.some(r => r.ngId === key && r.labelIndex === Number(lblIdx))) { - colorMap[lblIdx] = regions[key][lblIdx] - } else { - colorMap[lblIdx] = BACKUP_COLOR + this.defaultNgLayers$.pipe( + map(({ tmplAuxNgLayers }) => { + const returnVal: IColorMap = {} + for (const ngId in tmplAuxNgLayers) { + returnVal[ngId] = {} + const { auxMeshes } = tmplAuxNgLayers[ngId] + for (const auxMesh of auxMeshes) { + const { labelIndicies } = auxMesh + for (const lblIdx of labelIndicies) { + returnVal[ngId][lblIdx] = BACKUP_COLOR } } } - } - - for (const auxMeshes of auxMeshesArr) { - for (const key in auxMeshes) { - const existingObj = returnVal[key] || {} - returnVal[key] = { - ...existingObj, - ...auxMeshes[key], - } - } - } - this.activeColorMap = returnVal - return returnVal - }) - ) - - private auxMeshes$: Observable<IAuxMesh[]> = combineLatest([ - this.selectedTemplateSelector$, - this.selectedParcellation$, + return returnVal + }) + ) ]).pipe( - map(([ tmpl, parc ]) => { - const { auxMeshes: tmplAuxMeshes = [] as IAuxMesh[] } = tmpl || {} - const { auxMeshes: parclAuxMeshes = [] as IAuxMesh[] } = parc || {} - return [...tmplAuxMeshes, ...parclAuxMeshes] - }) + map(([cmParc, cmAux]) => ({ + ...cmParc, + ...cmAux + })) ) private sub: Subscription[] = [] - ngOnDestroy(){ + ngOnDestroy(): void{ while (this.sub.length > 0) this.sub.pop().unsubscribe() } - private pliVol$: Observable<string[]> = this._pliVol$ - ? this._pliVol$.pipe( - map(arr => { - const output = [] - for (const item of arr) { - for (const volume of item.data["iav-registered-volumes"].volumes) { - output.push(volume.name) - } - } - return output - }) - ) - : NEVER constructor( private store$: Store<any>, - private routerSvc: RouterService, - @Optional() @Inject(_PLI_VOLUME_INJ_TOKEN) private _pliVol$: Observable<_TPLIVal[]>, - @Optional() @Inject(REGION_OF_INTEREST) roi$: Observable<TRegionDetail> + private layerEffects: LayerCtrlEffects, ){ - if (roi$) { - - this.sub.push( - roi$.pipe( - switchMap(roi => { - if (!roi || !roi.hasRegionalMap) { - // clear pmap - return of(null) - } - - const { links } = roi - const { regional_map: regionalMapUrl, regional_map_info: regionalMapInfoUrl } = links - return from(fetch(regionalMapInfoUrl).then(res => res.json())).pipe( - map(regionalMapInfo => { - return { - roi, - regionalMapUrl, - regionalMapInfo - } - }) - ) - }) - ).subscribe(processedRoi => { - if (!processedRoi) { - this.store$.dispatch( - ngViewerActionRemoveNgLayer({ - layer: { - name: NehubaLayerControlService.PMAP_LAYER_NAME - } - }) - ) - return - } - const { - roi, - regionalMapUrl, - regionalMapInfo - } = processedRoi - const { min, max, colormap = EnumColorMapName.VIRIDIS } = regionalMapInfo || {} as any - - const shaderObj = { - ...PMAP_DEFAULT_CONFIG, - ...{ colormap }, - ...( typeof min !== 'undefined' ? { lowThreshold: min } : {} ), - ...( max ? { highThreshold: max } : { highThreshold: 1 } ) - } + this.sub.push( - const layer = { - name: NehubaLayerControlService.PMAP_LAYER_NAME, - source : `nifti://${regionalMapUrl}`, - mixability : 'nonmixable', - shader : getShader(shaderObj), + /** + * on store showdelin + * toggle parcnglayers visibility + */ + this.store$.pipe( + select(atlasAppearance.selectors.showDelineation), + withLatestFrom(this.defaultNgLayers$) + ).subscribe(([flag, { parcNgLayers }]) => { + const layerObj = {} + for (const key in parcNgLayers) { + layerObj[key] = { + visible: flag } + } - this.store$.dispatch( - ngViewerActionAddNgLayer({ layer }) - ) - - // this.layersService.highThresholdMap.set(layerName, highThreshold) - // this.layersService.lowThresholdMap.set(layerName, lowThreshold) - // this.layersService.colorMapMap.set(layerName, cmap) - // this.layersService.removeBgMap.set(layerName, removeBg) + this.manualNgLayersControl$.next({ + type: 'update', + payload: layerObj }) - ) - } - - this.sub.push( - this.ngLayers$.subscribe(({ ngLayers }) => { - this.ngLayersRegister.layers = ngLayers - }) + }), ) this.sub.push( - this.store$.pipe( - select(ngViewerSelectorClearView), - distinctUntilChanged() - ).subscribe(flag => { - const pmapLayer = this.ngLayersRegister.layers.find(l => l.name === NehubaLayerControlService.PMAP_LAYER_NAME) - if (!pmapLayer) return - const payload = { - type: 'update', - payload: { - [NehubaLayerControlService.PMAP_LAYER_NAME]: { - visible: !flag - } - } - } as TNgLayerCtrl<'update'> - this.manualNgLayersControl$.next(payload) + this.ngLayers$.subscribe(({ customLayers }) => { + this.ngLayersRegister = customLayers }) ) @@ -264,18 +177,17 @@ export class NehubaLayerControlService implements OnDestroy{ */ this.sub.push( this.store$.pipe( - select(viewerStateCustomLandmarkSelector), - withLatestFrom(this.auxMeshes$) - ).subscribe(([landmarks, auxMeshes]) => { - + select(annotation.selectors.annotations), + withLatestFrom(this.defaultNgLayers$) + ).subscribe(([landmarks, { tmplAuxNgLayers }]) => { const payload: { [key: string]: number } = {} const alpha = landmarks.length > 0 ? 0.2 : 1.0 - for (const auxMesh of auxMeshes) { - payload[auxMesh.ngId] = alpha + for (const ngId in tmplAuxNgLayers) { + payload[ngId] = alpha } this.manualNgLayersControl$.next({ @@ -286,98 +198,73 @@ export class NehubaLayerControlService implements OnDestroy{ ) } - public activeColorMap: IColorMap - - public overwriteColorMap$ = new BehaviorSubject<IColorMap>(null) - - public setColorMap$: Observable<IColorMap> = merge( - this.activeColorMap$.pipe( - // TODO this is a dirty fix - // it seems, sometimes, overwritecolormap and activecolormap can emit at the same time - // (e.g. when reg selection changes) - // this ensures that the activecolormap emits later, and thus take effect over overwrite colormap - debounceTime(16), - ), - this.overwriteColorMap$.pipe( - filter(v => !!v), - ) + public setColorMap$: Observable<IColorMap> = this.activeColorMap$.pipe( + debounceTime(16), ).pipe( shareReplay(1) ) - public expectedLayerNames$ = combineLatest([ - this.selectedTemplateSelector$, - this.auxMeshes$, - this.selParcNgIdMap$, - ]).pipe( - map(([ tmpl, auxMeshes, parcNgIdMap ]) => { - const ngIdSet = new Set<string>() - const { ngId } = tmpl - ngIdSet.add(ngId) - for (const auxMesh of auxMeshes) { - const { ngId } = auxMesh - ngIdSet.add(ngId as string) - } - for (const ngId of parcNgIdMap.keys()) { - ngIdSet.add(ngId) - } - return Array.from(ngIdSet) - }) - ) - - public visibleLayer$: Observable<string[]> = combineLatest([ - this.expectedLayerNames$, - this.pliVol$.pipe( - startWith([]) - ), - this.routerSvc.customRoute$.pipe( - startWith({}), - map(val => val['x-voi'] === "d71d369a-c401-4d7e-b97a-3fb78eed06c5" - ? ["VOI_1 (area V1)", "VOI_2 (area V2)"] - : []), - ) - ]).pipe( - map(([ expectedLayerNames, layerNames, voiLayers ]) => { - const ngIdSet = new Set<string>([...layerNames, ...expectedLayerNames, ...voiLayers]) - return Array.from(ngIdSet) + public expectedLayerNames$ = this.defaultNgLayers$.pipe( + map(({ parcNgLayers, tmplAuxNgLayers, tmplNgLayers }) => { + return [ + ...Object.keys(parcNgLayers), + ...Object.keys(tmplAuxNgLayers), + ...Object.keys(tmplNgLayers), + ] }) ) /** * define when shown segments should be updated */ + public _segmentVis$: Observable<string[]> = combineLatest([ + this.selectedATP$, + this.selectedRegion$ + ]).pipe( + map(() => ['']) + ) + public segmentVis$: Observable<string[]> = combineLatest([ /** * selectedRegions */ this.selectedRegion$, + this.customLayers$.pipe( + map(layers => layers.filter(l => l.clType === "customlayer/colormap").length > 0), + ), /** * if layer contains non mixable layer */ - this.store$.pipe( - select(ngViewerSelectorLayers), - map(layers => layers.findIndex(l => l.mixability === 'nonmixable') >= 0), + this.customLayers$.pipe( + map(layers => layers.filter(l => l.clType === "customlayer/nglayer").length > 0), ), - /** - * clearviewqueue, indicating something is controlling colour map - * show all seg - */ - this.store$.pipe( - select(ngViewerSelectorClearView), - distinctUntilChanged() - ) ]).pipe( - withLatestFrom(this.selectedParcellation$), - map(([[ regions, nonmixableLayerExists, clearViewFlag ], selParc]) => { - if (nonmixableLayerExists && !clearViewFlag) { + withLatestFrom(this.selectedATPR$), + map(([[ selectedRegions, customMapExists, nonmixableLayerExists ], { atlas, parcellation, template, regions }]) => { + /** + * if non mixable layer exist (e.g. pmap) + * and no custom color map exist + * hide all segmentations + */ + if (!customMapExists && nonmixableLayerExists) { return null } - const { ngId: defaultNgId } = selParc || {} - /* selectedregionindexset needs to be updated regardless of forceshowsegment */ - const selectedRegionIndexSet = new Set<string>(regions.map(({ngId = defaultNgId, labelIndex}) => serialiseParcellationRegion({ ngId, labelIndex }))) - if (selectedRegionIndexSet.size > 0 && !clearViewFlag) { - return [...selectedRegionIndexSet] + /** + * if custom map exists, roi is all regions + * otherwise, roi is only selectedRegions + */ + const roi = customMapExists ? regions : selectedRegions + + const roiIndexSet = new Set<string>( + roi.map(r => { + const ngId = getParcNgId(atlas, template, parcellation, r) + const label = getRegionLabelIndex(atlas, template, parcellation, r) + return ngId && label && serializeSegment(ngId, label) + }).filter(v => !!v) + ) + if (roiIndexSet.size > 0) { + return [...roiIndexSet] } else { return [] } @@ -388,38 +275,38 @@ export class NehubaLayerControlService implements OnDestroy{ * ngLayers controller */ - private ngLayersRegister: {layers: INgLayerInterface[]} = { - layers: [] - } - public removeNgLayers(layerNames: string[]) { - this.ngLayersRegister.layers - .filter(layer => layerNames?.findIndex(l => l === layer.name) >= 0) - .map(l => l.name) - .forEach(layerName => { - this.store$.dispatch(ngViewerActionRemoveNgLayer({ - layer: { - name: layerName - } - })) - }) - } - public addNgLayer(layers: INgLayerInterface[]){ - this.store$.dispatch(ngViewerActionAddNgLayer({ - layer: layers - })) + private ngLayersRegister: atlasAppearance.NgLayerCustomLayer[] = [] + + private getUpdatedCustomLayer(isSameLayer: (o: atlasAppearance.NgLayerCustomLayer, n: atlasAppearance.NgLayerCustomLayer) => boolean){ + return this.store$.pipe( + select(atlasAppearance.selectors.customLayers), + map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.NgLayerCustomLayer[]), + pairwise(), + map(([ oldCustomLayers, newCustomLayers ]) => { + return newCustomLayers.filter(n => oldCustomLayers.some(o => o.id === n.id && !isSameLayer(o, n))) + }), + filter(arr => arr.length > 0), + ) } - private ngLayers$ = this.store$.pipe( - select(ngViewerSelectorLayers), - map((ngLayers: INgLayerInterface[]) => { - const newLayers = ngLayers.filter(l => { - const registeredLayerNames = this.ngLayersRegister.layers.map(l => l.name) - return !registeredLayerNames.includes(l.name) + + private updateCustomLayerTransparency$ = this.getUpdatedCustomLayer((o, n) => o.opacity === n.opacity) + private updateCustomLayerColorMap$ = this.getUpdatedCustomLayer((o, n) => o.shader === n.shader) + + private ngLayers$ = this.customLayers$.pipe( + map(customLayers => customLayers.filter(l => l.clType === "customlayer/nglayer") as atlasAppearance.NgLayerCustomLayer[]), + distinctUntilChanged( + arrayEqual((o, n) => o.id === n.id) + ), + map(customLayers => { + const newLayers = customLayers.filter(l => { + const registeredLayerNames = this.ngLayersRegister.map(l => l.id) + return !registeredLayerNames.includes(l.id) }) - const removeLayers = this.ngLayersRegister.layers.filter(l => { - const stateLayerNames = ngLayers.map(l => l.name) - return !stateLayerNames.includes(l.name) + const removeLayers = this.ngLayersRegister.filter(l => { + const stateLayerNames = customLayers.map(l => l.id) + return !stateLayerNames.includes(l.id) }) - return { newLayers, removeLayers, ngLayers } + return { newLayers, removeLayers, customLayers } }), shareReplay(1) ) @@ -431,11 +318,9 @@ export class NehubaLayerControlService implements OnDestroy{ map(newLayers => { const newLayersObj: any = {} - newLayers.forEach(({ name, source, ...rest }) => newLayersObj[name] = { + newLayers.forEach(({ id, source, ...rest }) => newLayersObj[id] = { ...rest, source, - // source: getProxyUrl(source), - // ...getProxyOther({source}) }) return { @@ -448,14 +333,75 @@ export class NehubaLayerControlService implements OnDestroy{ map(({ removeLayers }) => removeLayers), filter(layers => layers.length > 0), map(removeLayers => { - const removeLayerNames = removeLayers.map(v => v.name) + const removeLayerNames = removeLayers.map(v => v.id) return { type: 'remove', payload: { names: removeLayerNames } } as TNgLayerCtrl<'remove'> }) ), + this.updateCustomLayerTransparency$.pipe( + map(layers => { + const payload: Record<string, number> = {} + for (const layer of layers) { + const opacity = layer.opacity ?? 0.8 + payload[layer.id] = opacity + } + return { + type: 'setLayerTransparency', + payload + } as TNgLayerCtrl<'setLayerTransparency'> + }) + ), + this.updateCustomLayerColorMap$.pipe( + map(layers => { + const payload: Record<string, string> = {} + for (const layer of layers) { + const shader = layer.shader ?? getShader() + payload[layer.id] = shader + } + return { + type: 'updateShader', + payload + } as TNgLayerCtrl<'updateShader'> + }) + ), this.manualNgLayersControl$, ).pipe( ) + + public visibleLayer$: Observable<string[]> = combineLatest([ + this.expectedLayerNames$.pipe( + map(expectedLayerNames => { + const ngIdSet = new Set<string>([...expectedLayerNames]) + return Array.from(ngIdSet) + }) + ), + this.ngLayers$.pipe( + map(({ customLayers }) => customLayers), + startWith([] as atlasAppearance.NgLayerCustomLayer[]), + map(customLayers => { + /** + * pmap control has its own visibility controller + */ + return customLayers + .map(l => l.id) + .filter(name => name !== PMAP_LAYER_NAME) + }) + ), + this.customLayers$.pipe( + map(cl => { + const otherColormapExist = cl.filter(l => l.clType === "customlayer/colormap").length > 0 + const pmapExist = cl.filter(l => l.clType === "customlayer/nglayer").length > 0 + return pmapExist && !otherColormapExist + }), + distinctUntilChanged(), + map(flag => flag + ? [ PMAP_LAYER_NAME ] + : [] + ) + ) + ]).pipe( + map(([ expectedLayerNames, customLayerNames, pmapName ]) => [...expectedLayerNames, ...customLayerNames, ...pmapName, ...AnnotationLayer.Map.keys()]) + ) } diff --git a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts index f6fc08184e0d31d4ed9229ca6840b9981ac8c4f2..1fa9f52ff34373fdb76dd62ddeb7acffc71ff091 100644 --- a/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts +++ b/src/viewerModule/nehuba/layerCtrl.service/layerCtrl.util.ts @@ -1,6 +1,7 @@ import { InjectionToken } from '@angular/core' import { strToRgb } from 'common/util' import { Observable } from 'rxjs' +import { atlasAppearance } from 'src/state' export interface IColorMap { [key: string]: { @@ -42,14 +43,17 @@ export interface INgLayerCtrl { names: string[] } add: { - [key: string]: INgLayerInterface + [key: string]: atlasAppearance.NgLayerCustomLayer } update: { - [key: string]: INgLayerInterface + [key: string]: Partial<atlasAppearance.NgLayerCustomLayer> } setLayerTransparency: { [key: string]: number } + updateShader: { + [key: string]: string + } } export type TNgLayerCtrl<T extends keyof INgLayerCtrl> = { @@ -61,14 +65,3 @@ export const SET_COLORMAP_OBS = new InjectionToken<Observable<IColorMap>>('SET_C export const SET_LAYER_VISIBILITY = new InjectionToken<Observable<string[]>>('SET_LAYER_VISIBILITY') export const SET_SEGMENT_VISIBILITY = new InjectionToken<Observable<string[]>>('SET_SEGMENT_VISIBILITY') export const NG_LAYER_CONTROL = new InjectionToken<TNgLayerCtrl<keyof INgLayerCtrl>>('NG_LAYER_CONTROL') - -export interface INgLayerInterface { - name: string // displayName - source: string - mixability: string // base | mixable | nonmixable - annotation?: string // - id?: string // unique identifier - visible?: boolean - shader?: string - transform?: any -} diff --git a/src/viewerModule/nehuba/layoutOverlay/index.ts b/src/viewerModule/nehuba/layoutOverlay/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..cd90fbdb2c5cb739e9eeb5759b61a57197100597 --- /dev/null +++ b/src/viewerModule/nehuba/layoutOverlay/index.ts @@ -0,0 +1,3 @@ +export { + NehubaLayoutOverlayModule +} from "./module" diff --git a/src/viewerModule/nehuba/layoutOverlay/module.ts b/src/viewerModule/nehuba/layoutOverlay/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..4160b8d12d401b9acd2d7a0fc65f3af288d9261d --- /dev/null +++ b/src/viewerModule/nehuba/layoutOverlay/module.ts @@ -0,0 +1,35 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { MatMenuModule } from "@angular/material/menu"; +import { ViewerCtrlModule } from "../viewerCtrl"; +import { NehubaLayoutOverlay } from "./nehuba.layoutOverlay/nehuba.layoutOverlay.component"; +import { QuickTourModule } from "src/ui/quickTour"; +import { SpinnerModule } from "src/components/spinner"; +import { UtilModule } from "src/util"; +import { WindowResizeModule } from "src/util/windowResize"; +import { LayoutModule } from "src/layouts/layout.module"; +import { MatButtonModule } from "@angular/material/button"; +import { MatTooltipModule } from "@angular/material/tooltip"; + +@NgModule({ + imports: [ + MatMenuModule, + CommonModule, + LayoutModule, + ViewerCtrlModule, + QuickTourModule, + SpinnerModule, + UtilModule, + WindowResizeModule, + MatButtonModule, + MatTooltipModule, + ], + declarations: [ + NehubaLayoutOverlay, + ], + exports: [ + NehubaLayoutOverlay + ] +}) + +export class NehubaLayoutOverlayModule{} \ No newline at end of file diff --git a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..93af83e0d5f39e69334043acd31657aa18231e11 --- /dev/null +++ b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.component.ts @@ -0,0 +1,304 @@ +import { AfterViewInit, ChangeDetectorRef, Component, Inject, OnDestroy } from "@angular/core"; +import { select, Store } from "@ngrx/store"; +import { combineLatest, fromEvent, interval, merge, Observable, of, Subject, Subscription } from "rxjs"; +import { userInterface } from "src/state"; +import { NehubaViewerUnit } from "../../nehubaViewer/nehubaViewer.component"; +import { NEHUBA_INSTANCE_INJTKN, takeOnePipe, getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree } from "../../util"; +import { QUICKTOUR_DESC, ARIA_LABELS, IDS } from 'common/constants' +import { IQuickTourData } from "src/ui/quickTour/constrants"; +import { debounce, debounceTime, distinctUntilChanged, filter, map, mapTo, switchMap, take } from "rxjs/operators"; + +@Component({ + selector: `nehuba-layout-overlay`, + templateUrl: `./nehuba.layoutOverlay.template.html`, + styleUrls: [ + `./nehuba.layoutOverlay.style.css` + ] +}) + +export class NehubaLayoutOverlay implements OnDestroy, AfterViewInit{ + + public ARIA_LABELS = ARIA_LABELS + public IDS = IDS + public currentPanelMode: userInterface.PanelMode = "FOUR_PANEL" + public currentOrder: string = '0123' + public currentHoveredIndex: number + + public quickTourSliceViewSlide: IQuickTourData = { + order: 1, + description: QUICKTOUR_DESC.SLICE_VIEW, + } + + public quickTour3dViewSlide: IQuickTourData = { + order: 2, + description: QUICKTOUR_DESC.PERSPECTIVE_VIEW, + } + + public quickTourIconsSlide: IQuickTourData = { + order: 3, + description: QUICKTOUR_DESC.VIEW_ICONS, + } + + ngOnDestroy(): void { + while(this.subscription.length > 0) this.subscription.pop().unsubscribe() + while(this.nehubaUnitSubs.length > 0) this.nehubaUnitSubs.pop().unsubscribe() + } + + ngAfterViewInit(): void { + this.setQuickTourPos() + } + + handleCycleViewEvent(): void { + if (this.currentPanelMode !== "SINGLE_PANEL") return + this.store$.dispatch( + userInterface.actions.cyclePanelMode() + ) + } + + public toggleMaximiseMinimise(index: number): void { + this.store$.dispatch( + userInterface.actions.toggleMaximiseView({ + targetIndex: index + }) + ) + } + + public zoomNgView(panelIndex: number, factor: number): void { + const ngviewer = this.nehubaUnit?.nehubaViewer?.ngviewer + if (!ngviewer) throw new Error(`ngviewer not defined!`) + + /** + * panelIndex < 3 === slice view + */ + if (panelIndex < 3) { + /** + * factor > 1 === zoom out + */ + ngviewer.navigationState.zoomBy(factor) + } else { + ngviewer.perspectiveNavigationState.zoomBy(factor) + } + } + + public quickTourOverwritingPos = { + 'dialog': { + left: '0px', + top: '0px', + }, + 'arrow': { + left: '0px', + top: '0px', + } + } + + setQuickTourPos(): void { + const { innerWidth, innerHeight } = window + this.quickTourOverwritingPos = { + 'dialog': { + left: `${innerWidth / 2}px`, + top: `${innerHeight / 2}px`, + }, + 'arrow': { + left: `${innerWidth / 2 - 48}px`, + top: `${innerHeight / 2 - 48}px`, + } + } + } + + public panelMode$ = this.store$.pipe( + select(userInterface.selectors.panelMode) + ) + + public panelOrder$ = this.store$.pipe( + select(userInterface.selectors.panelOrder), + ) + + public volumeChunkLoading$: Subject<boolean> = new Subject() + + constructor( + private store$: Store, + private cdr: ChangeDetectorRef, + @Inject(NEHUBA_INSTANCE_INJTKN) nehuba$: Observable<NehubaViewerUnit> + ){ + this.subscription.push( + nehuba$.subscribe(nehuba => { + this.nehubaUnit = nehuba + this.onNewNehubaUnit(nehuba) + }) + ) + } + + private onNewNehubaUnit(nehubaUnit: NehubaViewerUnit){ + + while(this.nehubaUnitSubs.length) this.nehubaUnitSubs.pop().unsubscribe() + this.nehubaViewPanels = [] + this.nanometersToOffsetPixelsFn = [] + + if (!nehubaUnit) { + return + } + + const removeExistingPanels = () => { + const element = nehubaUnit.nehubaViewer.ngviewer.layout.container.componentValue.element as HTMLElement + while (element.childElementCount > 0) { + element.removeChild(element.firstElementChild) + } + return element + } + + this.nehubaUnitSubs.push( + + /** + * sliceview loading event + */ + fromEvent<CustomEvent>( + nehubaUnit.elementRef.nativeElement, + 'sliceRenderEvent' + ).pipe( + map(ev => { + const { missingImageChunks, missingChunks } = ev.detail + return { missingImageChunks, missingChunks } + }), + distinctUntilChanged((o, n) => o.missingChunks === n.missingChunks && o.missingImageChunks === n.missingImageChunks) + ).subscribe(({ missingImageChunks, missingChunks }) => { + this.volumeChunkLoading$.next( + missingImageChunks > 0 || missingChunks > 0 + ) + this.detectChanges() + }), + + /** + * map slice view to weakmap + */ + fromEvent<CustomEvent>( + nehubaUnit.elementRef.nativeElement, + 'sliceRenderEvent' + ).pipe( + takeOnePipe() + ).subscribe(ev => { + for (const idx of [0, 1, 2]) { + const e = ev[idx] as CustomEvent + const el = e.target as HTMLElement + this.viewPanelWeakMap.set(el, idx) + this.nehubaViewPanels[idx] = el + this.nanometersToOffsetPixelsFn[idx] = e.detail.nanometersToOffsetPixels + } + }), + + + /** + * map perspective to weakmap + */ + fromEvent<CustomEvent>( + nehubaUnit.elementRef.nativeElement, + 'perpspectiveRenderEvent' + ).pipe( + take(1) + ).subscribe(ev => { + const perspPanel = ev.target as HTMLElement + this.nehubaViewPanels[3] = perspPanel + this.viewPanelWeakMap.set(perspPanel, 3) + }), + + /** + * on mouseover, emit hovered panel index + */ + fromEvent( + nehubaUnit.elementRef.nativeElement, + 'mouseover' + ).pipe( + switchMap((ev: MouseEvent) => merge( + of(this.findPanelIndex(ev.target as HTMLElement)), + fromEvent(nehubaUnit.elementRef.nativeElement, 'mouseout').pipe( + mapTo(null as number), + ), + )), + debounceTime(16), + ).subscribe(val => { + this.currentHoveredIndex = val + this.detectChanges() + }), + + /** + * on store change layout, update layout on nehuba + */ + combineLatest([ + this.panelMode$, + this.panelOrder$, + ]).pipe( + debounce(() => + nehubaUnit?.nehubaViewer?.ngviewer + ? of(true) + : interval(16).pipe( + filter(() => nehubaUnit?.nehubaViewer?.ngviewer), + take(1) + ) + ) + ).subscribe(([mode, panelOrder]) => { + + this.currentPanelMode = mode as userInterface.PanelMode + this.currentOrder = panelOrder + + const viewPanels = panelOrder.split('').map(v => Number(v)).map(idx => this.nehubaViewPanels[idx]) as [HTMLElement, HTMLElement, HTMLElement, HTMLElement] + + /** + * TODO smarter with event stream + */ + if (!viewPanels.every(v => !!v)) { + return + } + + switch (this.currentPanelMode) { + case "H_ONE_THREE": { + const element = removeExistingPanels() + const newEl = getHorizontalOneThree(viewPanels) + element.appendChild(newEl) + break; + } + case "V_ONE_THREE": { + const element = removeExistingPanels() + const newEl = getVerticalOneThree(viewPanels) + element.appendChild(newEl) + break; + } + case "FOUR_PANEL": { + const element = removeExistingPanels() + const newEl = getFourPanel(viewPanels) + element.appendChild(newEl) + break; + } + case "SINGLE_PANEL": { + const element = removeExistingPanels() + const newEl = getSinglePanel(viewPanels) + element.appendChild(newEl) + break; + } + default: + } + for (const panel of viewPanels) { + (panel as HTMLElement).classList.add('neuroglancer-panel') + } + + this.detectChanges() + nehubaUnit.redraw() + }) + ) + + this.detectChanges() + } + + public detectChanges(): void { + this.cdr.detectChanges() + } + + private nehubaUnit: NehubaViewerUnit + + private findPanelIndex = (panel: HTMLElement) => this.viewPanelWeakMap.get(panel) + private viewPanelWeakMap = new WeakMap<HTMLElement, number>() + private nehubaViewPanels: HTMLElement[] = [] + + private subscription: Subscription[] = [] + private nehubaUnitSubs: Subscription[] = [] + + private nanometersToOffsetPixelsFn: ((...arg: any[]) => any)[] = [] +} \ No newline at end of file diff --git a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.style.css b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.style.css new file mode 100644 index 0000000000000000000000000000000000000000..beea6a73ad8a3e22daa03c55a5d6022f175edda4 --- /dev/null +++ b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.style.css @@ -0,0 +1,41 @@ +:host +{ + pointer-events: none; +} + +current-layout +{ + display: block; + width: 100%; + height: 100%; + pointer-events: none; + left: 0; + top: 0; +} + +.layout-cell +{ + width: 100%; + height: 100%; +} + +.opacity-crossfade +{ + transition: opacity 170ms ease-in-out, + transform 250ms ease-in-out; +} + +.opacity-crossfade +{ + + opacity: 0.0; + pointer-events: none; +} + +.opacity-crossfade.onHover, +.opacity-crossfade:hover, +:host-context([ismobile="true"]) .opacity-crossfade.always-show-touchdevice +{ + opacity: 1.0 !important; + pointer-events: all !important; +} diff --git a/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html new file mode 100644 index 0000000000000000000000000000000000000000..e716c29fac236a4c7394b76a68a5027ffaf94646 --- /dev/null +++ b/src/viewerModule/nehuba/layoutOverlay/nehuba.layoutOverlay/nehuba.layoutOverlay.template.html @@ -0,0 +1,152 @@ +<current-layout + [current-layout-use-layout]="currentPanelMode" + [iav-key-listener]="[{ type: 'keydown', key: ' ', target: 'document', capture: true }]" + (iav-key-event)="handleCycleViewEvent()"> + <div class="layout-cell" cell-i + iav-window-resize + [iav-window-resize-time]="64" + (iav-window-resize-event)="setQuickTourPos()" + quick-tour + [quick-tour-description]="quickTourSliceViewSlide.description" + [quick-tour-order]="quickTourSliceViewSlide.order" + [quick-tour-overwrite-arrow]="sliceViewArrow" + [quick-tour-overwrite-position]="quickTourOverwritingPos"> + <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: currentOrder | getProperty : 0 | parseAsNumber }"></ng-content> + </div> + <div class="layout-cell" cell-ii> + <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: currentOrder | getProperty : 1 | parseAsNumber }"></ng-content> + </div> + <div class="layout-cell" cell-iii> + <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: currentOrder | getProperty : 2 | parseAsNumber }"></ng-content> + </div> + <div class="layout-cell" cell-iv + quick-tour + [quick-tour-description]="quickTour3dViewSlide.description" + [quick-tour-order]="quickTour3dViewSlide.order"> + <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: currentOrder | getProperty : 3 | parseAsNumber }"></ng-content> + </div> +</current-layout> + +<!-- slice view overlay tmpl --> +<ng-template #ngPanelOverlayTmpl let-panelIndex="panelIndex"> + + <!-- perspective view tmpl --> + <ng-template #overlayPerspectiveTmpl> + + <!-- mesh loading is still weird --> + <!-- if the precomputed server does not have the necessary fragment file, then the numberws will not collate --> + <!-- TODO --> + </ng-template> + + <iav-layout-fourcorners class="w-100 h-100 d-block"> + + <!-- panel controller --> + <div iavLayoutFourCornersBottomRight class="position-relative honing"> + + <ng-container *ngTemplateOutlet="panelCtrlTmpl; context: { + panelIndex: panelIndex, + visible: currentHoveredIndex === panelIndex + }"> + </ng-container> + + <div *ngIf="volumeChunkLoading$ | async" + class="position-absolute bottom-0 right-0"> + <spinner-cmp></spinner-cmp> + </div> + </div> + </iav-layout-fourcorners> + +</ng-template> + +<!-- panel control template --> +<ng-template + #panelCtrlTmpl + let-panelIndex="panelIndex" + let-visible="visible"> + <div class="ws-no-wrap opacity-crossfade always-show-touchdevice pe-all overlay-btn-container" + [ngClass]="{ onHover: visible }" + [attr.data-viewer-controller-visible]="visible" + [attr.data-viewer-controller-index]="panelIndex"> + + <div class="position-absolute w-100 h-100 pe-none" + *ngIf="panelIndex === 1" + quick-tour + [quick-tour-description]="quickTourIconsSlide.description" + [quick-tour-order]="quickTourIconsSlide.order"> + </div> + + <!-- perspective specific control --> + <ng-container *ngIf="panelIndex === 3"> + + <button mat-icon-button color="primary" + (click)="detectChanges()" + [matMenuTriggerFor]="viewerCtrlMenu"> + <i class="fas fa-cog"></i> + </button> + + </ng-container> + + <!-- factor < 1.0 === zoom in --> + <button mat-icon-button color="primary" + (click)="zoomNgView(panelIndex, 0.9)" + [attr.aria-label]="ARIA_LABELS.ZOOM_IN"> + <i class="fas fa-search-plus"></i> + </button> + + <!-- factor > 1.0 === zoom out --> + <button mat-icon-button color="primary" + (click)="zoomNgView(panelIndex, 1.1)" + [attr.aria-label]="ARIA_LABELS.ZOOM_OUT"> + <i class="fas fa-search-minus"></i> + </button> + + <ng-template + [ngTemplateOutlet]="maxBtnTmpl" + [ngTemplateOutletContext]="{ $implicit: panelIndex }"> + </ng-template> + </div> +</ng-template> + +<ng-template #maxBtnTmpl let-panelIndex> + <button + mat-icon-button + color="primary" + (click)="toggleMaximiseMinimise(panelIndex)"> + <ng-template + [ngIf]="currentPanelMode === 'SINGLE_PANEL'" + [ngIfElse]="expandTmpl"> + <i class="fas fa-compress"></i> + </ng-template> + + <ng-template #expandTmpl> + <i class="fas fa-expand"></i> + </ng-template> + </button> +</ng-template> + + +<!-- viewer ctrl --> +<mat-menu #viewerCtrlMenu> + <viewer-ctrl-component + class="d-block m-2 ml-3 mr-3" + (click)="$event.stopPropagation()"> + </viewer-ctrl-component> +</mat-menu> + +<ng-template #sliceViewArrow> + <svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path id="quarter_circle" d="M22.6151 96.5C22.6151 96.5 18.5 84.1266 18.5 76.5C18.5001 62 18.1151 59.5 22.6151 47C27.115 34.5 39.3315 27.7229 47.5 25C56.5 22 63 22.5 72.5 24C83.1615 25.6834 83.5 26 91 29" /> + <g id="arrow_top_left"> + <path id="arrow_stem" d="M37 40C35.5882 38.5882 17.6863 20.6863 12 15" /> + <path id="arrow_head" d="M6 24C6.38926 21.7912 6.68496 18.3286 6.71205 16.0803C6.73751 13.9665 6.632 13.6135 6.52632 11.5C6.46368 10.2469 6.52632 11.5 6 8C11 10 9.71916 9.74382 11 9.99999C13.5 10.5 13.743 10.7451 17 11C20 11.2348 21.1276 11 22 11" stroke-linecap="round" stroke-linejoin="round"/> + </g> + <g id="arrow_left"> + <path id="arrow_stem_2" d="M29.4229 78.5771C27.1573 78.5771 18.3177 78.5771 9.19238 78.5771" /> + <path id="arrow_head_2" d="M13.3137 89.6274C12.0271 87.7903 9.78778 85.1328 8.2171 83.5238C6.74048 82.0112 6.41626 81.8362 4.84703 80.4164C3.91668 79.5747 4.84703 80.4164 2 78.3137C6.94975 76.1924 5.86291 76.9169 6.94974 76.1924C9.07106 74.7782 9.41624 74.7797 11.8995 72.6569C14.1868 70.7016 14.8181 69.7382 15.435 69.1213" stroke-linecap="round" stroke-linejoin="round"/> + </g> + <g id="arrow_top"> + <path id="arrow_stem_3" d="M77.0057 32.0057C77.0057 30.3124 77.0057 16.2488 77.0057 9.42862" /> + <path id="arrow_head_3" d="M68.4342 11.1429C69.9189 10.1032 72.0665 8.29351 73.3667 7.02421C74.5891 5.83091 74.7305 5.5689 75.8779 4.30076C76.5581 3.54892 75.8779 4.30076 77.5771 2C79.2914 6.00002 78.7059 5.12172 79.2915 6.00002C80.4343 7.71431 80.4331 7.99326 82.1486 10C83.7287 11.8485 84.5072 12.3587 85.0058 12.8572" stroke-linecap="round" stroke-linejoin="round"/> + </g> + </svg> +</ng-template> \ No newline at end of file diff --git a/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.component.ts b/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.component.ts deleted file mode 100644 index 7aab44ac4156b05454a3d7bce915068cdf24a07e..0000000000000000000000000000000000000000 --- a/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Component, Input } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { Observable } from "rxjs"; -import { distinctUntilChanged, map } from "rxjs/operators"; -import { PANELS } from 'src/services/state/ngViewerState.store.helper' -import { ARIA_LABELS } from 'common/constants' -import { ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from "src/services/state/ngViewerState/selectors"; - -const { - MAXIMISE_VIEW, - UNMAXIMISE_VIEW, -} = ARIA_LABELS - -@Component({ - selector: 'maximise-panel-button', - templateUrl: './maximisePanelButton.template.html', - styleUrls: [ - './maximisePanelButton.style.css', - ], -}) - -export class MaximisePanelButton { - - public ARIA_LABEL_MAXIMISE_VIEW = MAXIMISE_VIEW - public ARIA_LABEL_UNMAXIMISE_VIEW = UNMAXIMISE_VIEW - - @Input() public panelIndex: number - - private panelMode$: Observable<string> - private panelOrder$: Observable<string> - - public isMaximised$: Observable<boolean> - - constructor( - private store$: Store<any>, - ) { - this.panelMode$ = this.store$.pipe( - select(ngViewerSelectorPanelMode), - distinctUntilChanged(), - ) - - this.panelOrder$ = this.store$.pipe( - select(ngViewerSelectorPanelOrder), - distinctUntilChanged(), - ) - - this.isMaximised$ = this.panelMode$.pipe( - map(panelMode => panelMode === PANELS.SINGLE_PANEL), - ) - } -} diff --git a/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.template.html b/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.template.html deleted file mode 100644 index 03cf3cd01e57071b42cb3234dfadfe6eab5f34b1..0000000000000000000000000000000000000000 --- a/src/viewerModule/nehuba/maximisePanelButton/maximisePanelButton.template.html +++ /dev/null @@ -1,17 +0,0 @@ -<ng-content *ngTemplateOutlet="maximiseBtnTmpl; context: { $implicit: (isMaximised$ | async) }"> -</ng-content> - -<ng-template #maximiseBtnTmpl let-ismaximised> - - <button - [matTooltip]="ismaximised ? 'Restore four panel view' : 'Maximise this panel'" - mat-icon-button - [attr.aria-label]="ismaximised ? ARIA_LABEL_UNMAXIMISE_VIEW : ARIA_LABEL_MAXIMISE_VIEW" - color="primary"> - <i *ngIf="ismaximised; else expandIconTemplate" class="fas fa-compress"></i> - </button> - - <ng-template #expandIconTemplate> - <i class="fas fa-expand"></i> - </ng-template> -</ng-template> diff --git a/src/viewerModule/nehuba/mesh.effects/mesh.effects.ts b/src/viewerModule/nehuba/mesh.effects/mesh.effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..74a109766a28373517447d90cea0fd804883a16d --- /dev/null +++ b/src/viewerModule/nehuba/mesh.effects/mesh.effects.ts @@ -0,0 +1,49 @@ +import { Injectable } from "@angular/core"; +import { createEffect } from "@ngrx/effects"; +import { Store } from "@ngrx/store"; +import { map, mapTo } from "rxjs/operators"; +import { actionSetAuxMeshes, IAuxMesh } from "../store"; +import { atlasSelection } from "src/state" +import { LayerCtrlEffects } from "../layerCtrl.service/layerCtrl.effects"; + +@Injectable() +export class MeshEffects{ + constructor( + private store: Store<any>, + private effect: LayerCtrlEffects, + ){ + + } + + onATPSelectResetAuxMeshes = createEffect(() => this.store.pipe( + atlasSelection.fromRootStore.distinctATP(), + mapTo( + actionSetAuxMeshes({ + payload: [] + }) + ) + )) + + onNgLayersEmitSetAuxMeshes = createEffect(() => this.effect.onATPDebounceNgLayers$.pipe( + map(({ tmplAuxNgLayers }) => { + const newAuxMeshes: IAuxMesh[] = [] + for (const key in tmplAuxNgLayers) { + + for (const mesh of tmplAuxNgLayers[key].auxMeshes) { + newAuxMeshes.push({ + "@id": `aux-mesh-${mesh.name}`, + ngId: key, + labelIndicies: mesh.labelIndicies, + name: mesh.name, + rgb: [255, 255, 255], + visible: true, + displayName: mesh.name + }) + } + } + return actionSetAuxMeshes({ + payload: newAuxMeshes + }) + }) + )) +} \ No newline at end of file diff --git a/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts b/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts index 967762e46acb61182a31dfc625772c14da8a2dca..ef3ac53320fbfa6ee86ec80ac140c105a2534af8 100644 --- a/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts +++ b/src/viewerModule/nehuba/mesh.service/mesh.service.spec.ts @@ -1,134 +1,76 @@ import { TestBed } from "@angular/core/testing" import { MockStore, provideMockStore } from "@ngrx/store/testing" import { hot } from "jasmine-marbles" -import { viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors" +import { NehubaMeshService } from "./mesh.service" +import { atlasSelection } from "src/state" +import { SapiRegionModel } from "src/atlasComponents/sapi" +import * as configSvc from "../config.service" +import { LayerCtrlEffects } from "../layerCtrl.service/layerCtrl.effects" +import { NEVER, of, pipe } from "rxjs" +import { mapTo, take } from "rxjs/operators" import { selectorAuxMeshes } from "../store" -import { getLayerNameIndiciesFromParcRs, collateLayerNameIndicies, findFirstChildrenWithLabelIndex, NehubaMeshService } from "./mesh.service" -const fits1 = { - ngId: 'foobar', - labelIndex: 123, - children: [] +const fits1 = {} as SapiRegionModel +const auxMesh = { + "@id": 'bla', + labelIndicies: [1,2,3], + name: 'bla', + ngId: 'bla', + rgb: [255, 255, 255] as [number, number, number], + visible: true, + displayName: 'bla' } -const fits1_1 = { - ngId: 'foobar', - labelIndex: 5, - children: [] -} - -const fits2 = { - ngId: 'helloworld', - labelIndex: 567, - children: [] -} - -const fits2_1 = { - ngId: 'helloworld', - labelIndex: 11, - children: [] -} - -const nofit1 = { - ngId: 'bazz', - children: [] -} - -const nofit2 = { - ngId: 'but', - children: [] -} - -describe('> mesh.server.ts', () => { - describe('> findFirstChildrenWithLabelIndex', () => { - it('> if root fits, return root', () => { - const result = findFirstChildrenWithLabelIndex({ - ...fits1, - children: [fits2] - }) - - expect(result).toEqual([{ - ...fits1, - children: [fits2] - }]) - }) - - it('> if root doesnt fit, will try to find the next node, until one fits', () => { - const result = findFirstChildrenWithLabelIndex({ - ...nofit1, - children: [fits1, fits2] - }) - expect(result).toEqual([fits1, fits2]) - }) - - it('> if notthings fits, will return empty array', () => { - const result = findFirstChildrenWithLabelIndex({ - ...nofit1, - children: [nofit1, nofit2] - }) - expect(result).toEqual([]) - }) +describe('> mesh.service.ts', () => { + let getParcNgIdSpy: jasmine.Spy = jasmine.createSpy('getParcNgId') + let getRegionLabelIndexSpy: jasmine.Spy = jasmine.createSpy('getRegionLabelIndexSpy') + let getATPSpy: jasmine.Spy = jasmine.createSpy('distinctATP') + + const mockAtlas = { + '@id': 'mockAtlas' + } + const mockTmpl = { + '@id': 'mockTmpl' + } + const mockParc = { + '@id': 'mockParc' + } + + beforeEach(() => { + spyOnProperty(configSvc, 'getParcNgId').and.returnValue(getParcNgIdSpy) + spyOnProperty(configSvc, 'getRegionLabelIndex').and.returnValue(getRegionLabelIndexSpy) + getATPSpy = spyOn(atlasSelection.fromRootStore, 'distinctATP') + getATPSpy.and.returnValue( + pipe( + mapTo({ + atlas: mockAtlas, + parcellation: mockParc, + template: mockTmpl + }) + ) + ) }) - describe('> collateLayerNameIndicies', () => { - it('> collates same ngIds', () => { - const result = collateLayerNameIndicies([ - fits1_1, fits1, fits2, fits2_1 - ]) - expect(result).toEqual({ - [fits1.ngId]: [fits1_1.labelIndex, fits1.labelIndex], - [fits2.ngId]: [fits2.labelIndex, fits2_1.labelIndex] - }) - }) + afterEach(() => { + getParcNgIdSpy.calls.reset() + getRegionLabelIndexSpy.calls.reset() + getATPSpy.calls.reset() }) - - describe('> getLayerNameIndiciesFromParcRs', () => { - const root = { - ...fits1, - children: [ - { - ...nofit1, - children: [ - { - ...fits1_1, - children: [ - fits2, fits2_1 - ] - } - ] - } - ] - } - const parc = { - regions: [ root ] - } - it('> if selectedRegion.length === 0, selects top most regions with labelIndex', () => { - const result = getLayerNameIndiciesFromParcRs(parc, []) - expect(result).toEqual({ - [root.ngId]: [root.labelIndex] - }) - }) - - it('> if selReg.length !== 0, select region ngId & labelIndex', () => { - const result = getLayerNameIndiciesFromParcRs(parc, [ fits1_1 ]) - expect(result).toEqual({ - [fits1_1.ngId]: [fits1_1.labelIndex] - }) - }) - }) - describe('> NehubaMeshService', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [ provideMockStore(), NehubaMeshService, + { + provide: LayerCtrlEffects, + useValue: { + onATPDebounceNgLayers$: NEVER + } + } ] }) - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectedParcellationSelector, {}) - mockStore.overrideSelector(viewerStateSelectedTemplateSelector, {}) }) it('> can be init', () => { @@ -136,39 +78,106 @@ describe('> mesh.server.ts', () => { expect(service).toBeTruthy() }) - it('> mixes in auxillaryMeshIndices', () => { - const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, [ fits1 ]) + describe("> loadMeshes$", () => { - mockStore.overrideSelector(selectorAuxMeshes, [{ - ngId: fits2.ngId, - labelIndicies: [11, 22], - "@id": '', - name: '', - rgb: [100, 100, 100], - visible: true, - displayName: '' - }]) + describe("> auxMesh defined", () => { + + const ngId = 'blabla' + const labelIndex = 12 + + beforeEach(() => { + + const mockStore = TestBed.inject(MockStore) + mockStore.overrideSelector(atlasSelection.selectors.selectedRegions, [ fits1 ]) + mockStore.overrideSelector(atlasSelection.selectors.selectedParcAllRegions, []) + mockStore.overrideSelector(selectorAuxMeshes, [auxMesh]) + + getParcNgIdSpy.and.returnValue(ngId) + getRegionLabelIndexSpy.and.returnValue(labelIndex) - const service = TestBed.inject(NehubaMeshService) - expect( - service.loadMeshes$ - ).toBeObservable( - hot('(ab)', { - a: { - layer: { - name: fits1.ngId - }, - labelIndicies: [ fits1.labelIndex ] - }, - b: { - layer: { - name: fits2.ngId, - }, - labelIndicies: [11, 22] - } }) - ) + + it("> auxMesh ngId labelIndex emitted", () => { + + const service = TestBed.inject(NehubaMeshService) + expect( + service.loadMeshes$ + ).toBeObservable( + hot('(ab)', { + a: { + layer: { + name: ngId + }, + labelIndicies: [ labelIndex ] + }, + b: { + layer: { + name: auxMesh.ngId, + }, + labelIndicies: auxMesh.labelIndicies + } + }) + ) + }) + }) + + describe("> if multiple ngid and labelindicies are present", () => { + + const ngId1 = 'blabla' + const labelIndex1 = 12 + + const ngId2 = 'foobar' + const labelIndex2 = 13 + + beforeEach(() => { + + const mockStore = TestBed.inject(MockStore) + mockStore.overrideSelector(atlasSelection.selectors.selectedRegions, [ fits1 ]) + mockStore.overrideSelector(atlasSelection.selectors.selectedParcAllRegions, [fits1, fits1]) + mockStore.overrideSelector(selectorAuxMeshes, []) + + getParcNgIdSpy.and.returnValues(ngId1, ngId2, ngId2) + getRegionLabelIndexSpy.and.returnValues(labelIndex1, labelIndex2, labelIndex2) + }) + + it('> should call getParcNgIdSpy and getRegionLabelIndexSpy thrice', () => { + const service = TestBed.inject(NehubaMeshService) + service.loadMeshes$.pipe( + take(1) + ).subscribe(() => { + + expect(getParcNgIdSpy).toHaveBeenCalledTimes(3) + expect(getRegionLabelIndexSpy).toHaveBeenCalledTimes(3) + }) + }) + + /** + * in the case of julich brain 2.9 in colin 27, we expect selecting a region will hide meshes from all relevant ngIds (both left and right) + */ + it('> expect the emitted value to be incl all ngIds', () => { + const service = TestBed.inject(NehubaMeshService) + expect( + service.loadMeshes$ + ).toBeObservable( + hot('(ab)', { + a: { + layer: { + name: ngId1 + }, + labelIndicies: [] + }, + b: { + layer: { + name: ngId2 + }, + labelIndicies: [ labelIndex2 ] + } + }) + ) + + }) + }) + }) }) }) diff --git a/src/viewerModule/nehuba/mesh.service/mesh.service.ts b/src/viewerModule/nehuba/mesh.service/mesh.service.ts index dd17226af19ad4c3ec2dfc8f10dbc79a96e53635..d372ce460746d01c5cf560518e1532f38616b882 100644 --- a/src/viewerModule/nehuba/mesh.service/mesh.service.ts +++ b/src/viewerModule/nehuba/mesh.service/mesh.service.ts @@ -1,56 +1,13 @@ import { Injectable, OnDestroy } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { combineLatest, Observable, of } from "rxjs"; -import { switchMap } from "rxjs/operators"; -import { viewerStateSelectedParcellationSelector, viewerStateSelectedRegionsSelector, viewerStateSelectedTemplateSelector } from "src/services/state/viewerState/selectors"; +import { combineLatest, merge, Observable, of } from "rxjs"; +import { map, switchMap } from "rxjs/operators"; import { IMeshesToLoad } from '../constants' -import { flattenReducer } from 'common/util' -import { IAuxMesh, selectorAuxMeshes, actionSetAuxMeshes } from "../store"; - -interface IRegion { - ngId?: string - labelIndex?: number - children: IRegion[] -} - -interface IParc { - ngId?: string - regions: IRegion[] -} - -type TCollatedLayerNameIdx = { - [key: string]: number[] -} - -export function findFirstChildrenWithLabelIndex(region: IRegion): IRegion[]{ - if (region.ngId && region.labelIndex) { - return [ region ] - } - return region.children - .map(findFirstChildrenWithLabelIndex) - .reduce(flattenReducer, []) -} - -export function collateLayerNameIndicies(regions: IRegion[]){ - const returnObj: TCollatedLayerNameIdx = {} - for (const r of regions) { - if (returnObj[r.ngId]) { - returnObj[r.ngId].push(r.labelIndex) - } else { - returnObj[r.ngId] = [r.labelIndex] - } - } - return returnObj -} - -export function getLayerNameIndiciesFromParcRs(parc: IParc, rs: IRegion[]): TCollatedLayerNameIdx { - - const arrOfRegions = (rs.length === 0 ? parc.regions : rs) - .map(findFirstChildrenWithLabelIndex) - .reduce(flattenReducer, []) as IRegion[] - - return collateLayerNameIndicies(arrOfRegions) -} +import { selectorAuxMeshes } from "../store"; +import { LayerCtrlEffects } from "../layerCtrl.service/layerCtrl.effects"; +import { atlasSelection } from "src/state"; +import { Tree } from "src/components/flatHierarchy/treeView/treeControl" +import { getParcNgId, getRegionLabelIndex } from "../config.service"; /** * control mesh loading etc @@ -62,98 +19,113 @@ export class NehubaMeshService implements OnDestroy { private onDestroyCb: (() => void)[] = [] constructor( - private store$: Store<any> + private store$: Store<any>, + private effect: LayerCtrlEffects, ){ - const auxMeshSub = combineLatest([ - this.selectedTemplate$, - this.selectedParc$ - ]).subscribe(([ tmpl, parc ]) => { - const { auxMeshes: tmplAuxMeshes = [] as IAuxMesh[] } = tmpl || {} - const { auxMeshes: parcAuxMeshes = [] as IAuxMesh[]} = parc || {} - this.store$.dispatch( - actionSetAuxMeshes({ - payload: [...tmplAuxMeshes, ...parcAuxMeshes] - }) - ) - }) - this.onDestroyCb.push(() => auxMeshSub.unsubscribe()) } - ngOnDestroy(){ + ngOnDestroy(): void { while(this.onDestroyCb.length > 0) this.onDestroyCb.pop()() } - private selectedTemplate$ = this.store$.pipe( - select(viewerStateSelectedTemplateSelector) - ) - - private selectedRegions$ = this.store$.pipe( - select(viewerStateSelectedRegionsSelector) - ) - - private selectedParc$ = this.store$.pipe( - select(viewerStateSelectedParcellationSelector) - ) - private auxMeshes$ = this.store$.pipe( - select(selectorAuxMeshes), + public auxMeshes$ = this.effect.onATPDebounceNgLayers$.pipe( + map(({ tmplAuxNgLayers }) => tmplAuxNgLayers) ) - public loadMeshes$: Observable<IMeshesToLoad> = combineLatest([ - this.auxMeshes$, - this.selectedTemplate$, - this.selectedParc$, - this.selectedRegions$, - ]).pipe( - switchMap(([auxMeshes, template, parc, selRegions]) => { - - /** - * if colin 27 and julich brain 2.9.0, select all regions - */ - let overrideSelRegion = null - if ( - template['@id'] === 'minds/core/referencespace/v1.0.0/7f39f7be-445b-47c0-9791-e971c0b6d992' && - parc['@id'] === 'minds/core/parcellationatlas/v1.0.0/94c1125b-b87e-45e4-901c-00daee7f2579-290' - ) { - overrideSelRegion = [] - } - - const obj = getLayerNameIndiciesFromParcRs(parc, overrideSelRegion || selRegions) - const { auxillaryMeshIndices = [] } = parc - const arr: IMeshesToLoad[] = [] - for (const key in obj) { - const labelIndicies = Array.from(new Set([...obj[key], ...auxillaryMeshIndices])) - arr.push({ - layer: { - name: key - }, - labelIndicies - }) - } - - const auxLayers: { - [key: string]: number[] - } = {} + public loadMeshes$: Observable<IMeshesToLoad> = merge( + combineLatest([ + this.store$.pipe( + atlasSelection.fromRootStore.distinctATP(), + ), + this.store$.pipe( + select(atlasSelection.selectors.selectedParcAllRegions), + ), + this.store$.pipe( + select(atlasSelection.selectors.selectedRegions), + ) + ]).pipe( + switchMap(([{ atlas, template, parcellation }, regions, selectedRegions]) => { + const ngIdRecord: Record<string, number[]> = {} + + const tree = new Tree( + regions, + (c, p) => (c.hasParent || []).some(_p => _p["@id"] === p["@id"]) + ) + + for (const r of regions) { + const regionLabelIndex = getRegionLabelIndex( atlas, template, parcellation, r ) + if (!regionLabelIndex) { + continue + } + if ( + tree.someAncestor(r, anc => !!getRegionLabelIndex(atlas, template, parcellation, anc)) + ) { + continue + } + const ngId = getParcNgId(atlas, template, parcellation, r) + if (!ngIdRecord[ngId]) { + ngIdRecord[ngId] = [] + } + ngIdRecord[ngId].push(regionLabelIndex) + } - for (const auxMesh of auxMeshes) { - const { name, ngId, labelIndicies } = auxMesh - if (!auxLayers[ngId]) { - auxLayers[ngId] = [] + if (selectedRegions.length > 0) { + /** + * If regions are selected, reset the meshes + */ + for (const key in ngIdRecord) { + ngIdRecord[key] = [] + } + + /** + * only show selected region + */ + for (const r of selectedRegions) { + const ngId = getParcNgId(atlas, template, parcellation, r) + const regionLabelIndex = getRegionLabelIndex( atlas, template, parcellation, r ) + if (!ngIdRecord[ngId]) { + ngIdRecord[ngId] = [] + } + ngIdRecord[ngId].push(regionLabelIndex) + } } - if (auxMesh.visible) { - auxLayers[ngId].push(...labelIndicies) + const arr: IMeshesToLoad[] = [] + + for (const ngId in ngIdRecord) { + const labelIndicies = ngIdRecord[ngId] + arr.push({ + labelIndicies, + layer: { name: ngId } + }) } - } - for (const key in auxLayers) { - arr.push({ - layer: { - name: key - }, - labelIndicies: auxLayers[key] - }) - } - - return of(...arr) - }), + + return of(...arr) + }) + ), + this.store$.pipe( + select(selectorAuxMeshes), + switchMap(auxMeshes => { + const obj: Record<string, number[]> = {} + const arr: IMeshesToLoad[] = [] + for (const mesh of auxMeshes) { + if (!obj[mesh.ngId]) { + obj[mesh.ngId] = [] + } + if (mesh.visible) { + obj[mesh.ngId].push(...mesh.labelIndicies) + } + } + for (const key in obj) { + arr.push({ + layer: { + name: key + }, + labelIndicies: obj[key] + }) + } + return of(...arr) + }) + ) ) } diff --git a/src/viewerModule/nehuba/module.ts b/src/viewerModule/nehuba/module.ts index 4e45d608c551ffce7fc8b0e8a2532da8159fc8d3..087c53efda843dfa3eb33112d666fbc6746af4f6 100644 --- a/src/viewerModule/nehuba/module.ts +++ b/src/viewerModule/nehuba/module.ts @@ -10,12 +10,8 @@ import { NEHUBA_VIEWER_FEATURE_KEY } from "./constants"; import { reducer } from "./store"; import { NehubaGlueCmp } from "./nehubaViewerGlue/nehubaViewerGlue.component"; import { UtilModule } from "src/util"; -import { LayoutModule } from "src/layouts/layout.module"; -import { TouchSideClass } from "./touchSideClass.directive"; import { ComponentsModule } from "src/components"; import { AngularMaterialModule } from "src/sharedModules"; -import { MaximisePanelButton } from "./maximisePanelButton/maximisePanelButton.component"; -import { Landmark2DModule } from "src/ui/nehubaContainer/2dLandmarks/module"; import { MouseoverModule } from "src/mouseoverModule"; import { StatusCardComponent } from "./statusCard/statusCard.component"; import { ShareModule } from "src/share"; @@ -25,8 +21,13 @@ import { StateModule } from "src/state"; import { AuthModule } from "src/auth"; import {QuickTourModule} from "src/ui/quickTour/module"; import { WindowResizeModule } from "src/util/windowResize"; -import { ViewerCtrlModule } from "./viewerCtrl"; import { DragDropFileModule } from "src/dragDropFile/module"; +import { NgLayerCtrlCmp } from "./ngLayerCtl/ngLayerCtrl.component"; +import { EffectsModule } from "@ngrx/effects"; +import { MeshEffects } from "./mesh.effects/mesh.effects"; +import { NehubaLayoutOverlayModule } from "./layoutOverlay"; +import { NgAnnotationService } from "./annotation/service"; +import { NgAnnotationEffects } from "./annotation/effects"; @NgModule({ imports: [ @@ -34,14 +35,11 @@ import { DragDropFileModule } from "src/dragDropFile/module"; FormsModule, ReactiveFormsModule, UtilModule, - LayoutModule, AngularMaterialModule, - Landmark2DModule, ComponentsModule, MouseoverModule, ShareModule, WindowResizeModule, - ViewerCtrlModule, DragDropFileModule, /** @@ -54,24 +52,30 @@ import { DragDropFileModule } from "src/dragDropFile/module"; NEHUBA_VIEWER_FEATURE_KEY, reducer ), - QuickTourModule + EffectsModule.forFeature([ + MeshEffects, + NgAnnotationEffects, + ]), + QuickTourModule, + NehubaLayoutOverlayModule, ], declarations: [ NehubaViewerContainerDirective, NehubaViewerUnit, NehubaViewerTouchDirective, NehubaGlueCmp, - TouchSideClass, - MaximisePanelButton, StatusCardComponent, + NgLayerCtrlCmp, ], exports: [ NehubaViewerUnit, NehubaViewerTouchDirective, NehubaGlueCmp, StatusCardComponent, + NgLayerCtrlCmp, ], providers: [ + { provide: IMPORT_NEHUBA_INJECT_TOKEN, useFactory: importNehubaFactory, @@ -80,11 +84,16 @@ import { DragDropFileModule } from "src/dragDropFile/module"; { provide: NEHUBA_INSTANCE_INJTKN, useValue: new BehaviorSubject(null) - } + }, + NgAnnotationService ], schemas: [ CUSTOM_ELEMENTS_SCHEMA ] }) -export class NehubaModule{} +export class NehubaModule{ + + // eslint-disable-next-line @typescript-eslint/no-empty-function + constructor(_svc: NgAnnotationService){} +} diff --git a/src/viewerModule/nehuba/navigation.service/navigation.effects.ts b/src/viewerModule/nehuba/navigation.service/navigation.effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae3c4d4e977b6190f2303bc8d2b00ccf72fd3084 --- /dev/null +++ b/src/viewerModule/nehuba/navigation.service/navigation.effects.ts @@ -0,0 +1,120 @@ +import { Inject, Injectable, OnDestroy } from "@angular/core"; +import { Actions, createEffect, ofType } from "@ngrx/effects"; +import { select, Store } from "@ngrx/store"; +import { combineLatest, Observable, Subscription } from "rxjs"; +import { filter, map, mapTo, tap, withLatestFrom } from "rxjs/operators"; +import { atlasSelection, MainState, userInterface, userPreference } from "src/state" +import { CYCLE_PANEL_MESSAGE } from "src/util/constants"; +import { timedValues } from "src/util/generator"; +import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; +import { NEHUBA_INSTANCE_INJTKN } from "../util"; +import { navAdd, navMul } from "./navigation.util"; + +@Injectable() +export class NehubaNavigationEffects implements OnDestroy{ + + private subscription: Subscription[] = [] + private nehubaInst: NehubaViewerUnit + private rafRef: number + + /** + * This is an implementation which reconciles local state with the global navigation state. + * - it should **never** set global state. + * - there exist two (potential) source of navigation state change + * - directly set via global state (with statuscard, url, etc) + * - via viewer (mostly through user interaction) + * if the latter were emitted, it is the local's responsibility to check the (debounced) diff between local/global state, + * and update global state accordingly. + * - This effect updates the internal navigation state. It should leave reporting any diff to the local viewer's native implementation. + */ + onNavigateTo = createEffect(() => this.action.pipe( + ofType(atlasSelection.actions.navigateTo), + filter(() => !!this.nehubaInst), + withLatestFrom( + this.store.pipe( + select(userPreference.selectors.useAnimation) + ), + this.store.pipe( + select(atlasSelection.selectors.navigation) + ) + ), + tap(([{ navigation, animation, physical }, globalAnimationFlag, currentNavigation]) => { + if (!animation || !globalAnimationFlag) { + this.nehubaInst.setNavigationState({ + ...navigation, + positionReal: physical + }) + return + } + + const gen = timedValues() + const src = currentNavigation + + const dest = { + ...src, + ...navigation + } + + const delta = navAdd(dest, navMul(src, -1)) + + const animate = () => { + + /** + * if nehubaInst becomes nullish whilst animation is running + */ + if (!this.nehubaInst) { + this.rafRef = null + return + } + + const next = gen.next() + const d = next.value + + const n = navAdd(src, navMul(delta, d)) + this.nehubaInst.setNavigationState({ + ...n, + positionReal: true + }) + + if ( !next.done ) { + this.rafRef = requestAnimationFrame(() => animate()) + } else { + this.rafRef = null + } + } + this.rafRef = requestAnimationFrame(() => animate()) + + }) + ), { dispatch: false }) + + onMaximise = createEffect(() => combineLatest([ + this.store.pipe( + select(userPreference.selectors.useMobileUi), + ), + this.store.pipe( + select(userInterface.selectors.panelMode), + map(mode => mode === "SINGLE_PANEL") + ) + ]).pipe( + filter(([ useMobileUi, singlePanelMode ]) => singlePanelMode && !useMobileUi), + mapTo( + userInterface.actions.snackBarMessage({ + message: CYCLE_PANEL_MESSAGE + }) + ) + )) + + constructor( + private action: Actions, + private store: Store<MainState>, + @Inject(NEHUBA_INSTANCE_INJTKN) nehubaInst$: Observable<NehubaViewerUnit>, + ){ + this.subscription.push( + nehubaInst$.subscribe(val => this.nehubaInst = val), + ) + } + + ngOnDestroy(): void { + while(this.subscription.length > 0) this.subscription.pop().unsubscribe() + } +} \ No newline at end of file diff --git a/src/viewerModule/nehuba/navigation.service/navigation.service.spec.ts b/src/viewerModule/nehuba/navigation.service/navigation.service.spec.ts index 9fdde1ebbab0cd2c3a8d71c01d935d9b175b897c..9180048b03a28473737f81fa49eb4d1800fcb87b 100644 --- a/src/viewerModule/nehuba/navigation.service/navigation.service.spec.ts +++ b/src/viewerModule/nehuba/navigation.service/navigation.service.spec.ts @@ -1,12 +1,11 @@ import { discardPeriodicTasks, fakeAsync, TestBed, tick } from '@angular/core/testing' import { MockStore, provideMockStore } from '@ngrx/store/testing' import { BehaviorSubject, of, Subject } from 'rxjs' -import { selectViewerConfigAnimationFlag } from 'src/services/state/viewerConfig/selectors' -import { viewerStateSelectorNavigation } from 'src/services/state/viewerState/selectors' import * as NavUtil from './navigation.util' import { NehubaViewerUnit } from '../nehubaViewer/nehubaViewer.component' import { NEHUBA_INSTANCE_INJTKN } from '../util' import { NehubaNavigationService } from './navigation.service' +import { userPreference, atlasSelection } from "src/state" const nav1 = { position: [1,2,3], @@ -63,11 +62,11 @@ describe('> navigation.service.ts', () => { const mockStore = TestBed.inject(MockStore) mockStore.overrideSelector( - viewerStateSelectorNavigation, + atlasSelection.selectors.navigation, nav1 ) mockStore.overrideSelector( - selectViewerConfigAnimationFlag, + userPreference.selectors.useAnimation, true ) }) @@ -108,7 +107,7 @@ describe('> navigation.service.ts', () => { service['nehubaViewerInstance'] = nehubaInst as NehubaViewerUnit const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectorNavigation, nav1) + mockStore.overrideSelector(atlasSelection.selectors.navigation, nav1) dispatchSpy = spyOn(mockStore, 'dispatch').and.callFake(() => {}) }) diff --git a/src/viewerModule/nehuba/navigation.service/navigation.service.ts b/src/viewerModule/nehuba/navigation.service/navigation.service.ts index ed751e8b85149abb089fbd5cf5adf0ec888abdee..e06ddd27fe488f686aec8dcd9c803491df1fad60 100644 --- a/src/viewerModule/nehuba/navigation.service/navigation.service.ts +++ b/src/viewerModule/nehuba/navigation.service/navigation.service.ts @@ -2,13 +2,11 @@ import { Inject, Injectable, OnDestroy, Optional } from "@angular/core"; import { select, Store } from "@ngrx/store"; import { Observable, ReplaySubject, Subscription } from "rxjs"; import { debounceTime } from "rxjs/operators"; -import { selectViewerConfigAnimationFlag } from "src/services/state/viewerConfig/selectors"; -import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions"; -import { viewerStateSelectorNavigation } from "src/services/state/viewerState/selectors"; import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; import { NEHUBA_INSTANCE_INJTKN } from "../util"; -import { timedValues } from 'src/util/generator' -import { INavObj, navAdd, navMul, navObjEqual } from './navigation.util' +import { INavObj, navObjEqual } from './navigation.util' +import { actions } from "src/state/atlasSelection"; +import { atlasSelection, userPreference } from "src/state"; @Injectable() export class NehubaNavigationService implements OnDestroy{ @@ -33,7 +31,7 @@ export class NehubaNavigationService implements OnDestroy{ ){ this.subscriptions.push( this.store$.pipe( - select(selectViewerConfigAnimationFlag) + select(userPreference.selectors.useAnimation) ).subscribe(flag => this.globalAnimationFlag = flag) ) @@ -52,59 +50,28 @@ export class NehubaNavigationService implements OnDestroy{ this.subscriptions.push( // realtime state nav state this.store$.pipe( - select(viewerStateSelectorNavigation) + select(atlasSelection.selectors.navigation) ).subscribe(v => { this.storeNav = v // if stored nav differs from viewerNav if (!this.viewerNavLock && this.nehubaViewerInstance) { const navEql = navObjEqual(this.storeNav, this.viewerNav) if (!navEql) { - this.navigateViewer({ - ...this.storeNav, - positionReal: true - }) + this.navigateViewer(this.storeNav) } } }) ) } - navigateViewer(navigation: INavObj & { positionReal?: boolean, animation?: any }){ + navigateViewer(navigation: INavObj): void { if (!navigation) return - const { animation, ...rest } = navigation - if (animation && this.globalAnimationFlag) { - - const gen = timedValues() - const src = this.viewerNav - - const dest = { - ...src, - ...navigation - } - - const delta = navAdd(dest, navMul(src, -1)) - - const animate = () => { - const next = gen.next() - const d = next.value - - const n = navAdd(src, navMul(delta, d)) - this.nehubaViewerInstance.setNavigationState({ - ...n, - positionReal: true - }) - - if ( !next.done ) { - this.rafRef = requestAnimationFrame(() => animate()) - } - } - this.rafRef = requestAnimationFrame(() => animate()) - } else { - this.nehubaViewerInstance.setNavigationState(rest) - } + // TODO + // readd consider how to do animation + this.nehubaViewerInstance.setNavigationState(navigation) } - setupViewerSub(){ + setupViewerSub(): void { this.viewerInstanceSubscriptions.push( // realtime viewer nav state this.nehubaViewerInstance.viewerPositionChange.subscribe( @@ -134,7 +101,7 @@ export class NehubaNavigationService implements OnDestroy{ if (!navEql) { this.store$.dispatch( - viewerStateChangeNavigation({ + actions.setNavigation({ navigation: roundedNav }) ) @@ -143,11 +110,11 @@ export class NehubaNavigationService implements OnDestroy{ ) } - clearViewerSub(){ + clearViewerSub(): void { while (this.viewerInstanceSubscriptions.length > 0) this.viewerInstanceSubscriptions.pop().unsubscribe() } - ngOnDestroy(){ + ngOnDestroy(): void { this.clearViewerSub() while (this.subscriptions.length > 0) this.subscriptions.pop().unsubscribe() } diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts index 304ef3b4b0b15be8061ffc4e614c1291e9aa07e9..c6437fc7c93844af930c70ce2c5ae019227f76cf 100644 --- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts +++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts @@ -1,12 +1,10 @@ -import { TestBed, async, fakeAsync, tick, ComponentFixture } from "@angular/core/testing" -import { CommonModule, DOCUMENT } from "@angular/common" +import { TestBed, fakeAsync, tick, ComponentFixture } from "@angular/core/testing" +import { CommonModule } from "@angular/common" import { NehubaViewerUnit, IMPORT_NEHUBA_INJECT_TOKEN, scanFn } from "./nehubaViewer.component" -import { importNehubaFactory } from "../util" import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service" import { LoggingModule, LoggingService } from "src/logging" -import { APPEND_SCRIPT_TOKEN, appendScriptFactory } from "src/util/constants" import { IMeshesToLoad, SET_MESHES_TO_LOAD } from "../constants" -import { ReplaySubject, Subject } from "rxjs" +import { Subject } from "rxjs" import { IColorMap, SET_COLORMAP_OBS, SET_LAYER_VISIBILITY } from "../layerCtrl.service" describe('> nehubaViewer.component.ts', () => { @@ -78,14 +76,12 @@ describe('> nehubaViewer.component.ts', () => { }) describe('> NehubaViewerUnit', () => { - let provideSetMeshToLoadCtrl = true - let provideLayerVisibility = true - let provideSetColorObs = true const setMeshToLoadCtl$ = new Subject<IMeshesToLoad>() - let setLayerVisibility$: Subject<string[]> - let setcolorMap$: Subject<IColorMap> - beforeEach(async(() => { - TestBed.configureTestingModule({ + let setLayerVisibility$: Subject<string[]> = new Subject() + let setcolorMap$: Subject<IColorMap> = new Subject() + let fixture: ComponentFixture<NehubaViewerUnit> + beforeEach(async () => { + await TestBed.configureTestingModule({ imports: [ CommonModule, LoggingModule @@ -100,111 +96,41 @@ describe('> nehubaViewer.component.ts', () => { }, { provide: SET_MESHES_TO_LOAD, - useFactory: () => provideSetMeshToLoadCtrl - ? setMeshToLoadCtl$ - : null + useFactory: () => setMeshToLoadCtl$ }, { provide: SET_LAYER_VISIBILITY, - useFactory: () => { - setLayerVisibility$ = provideLayerVisibility - ? new ReplaySubject(1) - : null - return setLayerVisibility$ - } + useValue: setLayerVisibility$ }, { provide: SET_COLORMAP_OBS, - useFactory: () => { - setcolorMap$ = provideSetColorObs - ? new ReplaySubject(1) - : null - return setcolorMap$ - } + useValue: setcolorMap$ }, AtlasWorkerService, LoggingService, ] }).compileComponents() - })) + }) it('> creates component', () => { - const fixture = TestBed.createComponent(NehubaViewerUnit) + fixture = TestBed.createComponent(NehubaViewerUnit) expect(fixture.componentInstance).toBeTruthy() }) - describe('> on create', () => { - it('> calls onInit lifecycle param properly', () => { - const onInitSpy = jasmine.createSpy('onInit') - const fixture = TestBed.createComponent(NehubaViewerUnit) - fixture.componentInstance.lifecycle = { - onInit: onInitSpy - } - - fixture.detectChanges() - - expect(onInitSpy).toHaveBeenCalled() - }) - }) - describe('> loading meshes', () => { - describe('> native', () => { - beforeAll(() => { - provideSetMeshToLoadCtrl = false - }) - it('> on loadMeshes$ emit, calls nehubaViewer.setMeshesToLoad', fakeAsync(() => { - - const fixture = TestBed.createComponent(NehubaViewerUnit) - fixture.componentInstance.nehubaViewer = { - setMeshesToLoad: jasmine.createSpy('setMeshesToLoad').and.returnValue(null), - dispose: () => {} - } - - fixture.detectChanges() - fixture.componentInstance['loadMeshes$'].next({ - layer: { - name: 'foo-bar' - }, - labelIndicies: [1,2,3] - }) - tick(1000) - expect(fixture.componentInstance.nehubaViewer.setMeshesToLoad).toHaveBeenCalledWith([1,2,3], { name: 'foo-bar' }) - })) + beforeEach(() => { + fixture = TestBed.createComponent(NehubaViewerUnit) + fixture.componentInstance.nehubaViewer = { + setMeshesToLoad: jasmine.createSpy('setMeshesToLoad').and.returnValue(null), + dispose: () => {} + } + fixture.componentInstance['_nehubaReady'] = true }) describe('> injecting SET_MESHES_TO_LOAD', () => { - beforeAll(() => { - provideSetMeshToLoadCtrl = true - }) - it('> navtive loadMeshes method will not trigger loadMesh call',fakeAsync(() => { - - const fixture = TestBed.createComponent(NehubaViewerUnit) - fixture.detectChanges() - const setMeshToLoadSpy = jasmine.createSpy('setMeshesToLoad').and.returnValue(null) - fixture.componentInstance.nehubaViewer = { - setMeshesToLoad: setMeshToLoadSpy, - dispose: () => {} - } - - fixture.detectChanges() - fixture.componentInstance['loadMeshes$'].next({ - layer: { - name: 'foo-bar' - }, - labelIndicies: [1,2,3] - }) - tick(1000) - expect(setMeshToLoadSpy).not.toHaveBeenCalledWith([1,2,3], { name: 'foo-bar' }) - })) it('> when injected obs emits, will trigger loadMesh call', fakeAsync(() => { - const fixture = TestBed.createComponent(NehubaViewerUnit) - fixture.componentInstance.nehubaViewer = { - setMeshesToLoad: jasmine.createSpy('setMeshesToLoad').and.returnValue(null), - dispose: () => {} - } - fixture.detectChanges() setMeshToLoadCtl$.next({ labelIndicies: [1,2,3], @@ -212,7 +138,7 @@ describe('> nehubaViewer.component.ts', () => { name: 'foo-bar' } }) - tick(1000) + tick(400) expect(fixture.componentInstance.nehubaViewer.setMeshesToLoad).toHaveBeenCalledWith([1,2,3], { name: 'foo-bar' }) })) }) @@ -250,12 +176,12 @@ describe('> nehubaViewer.component.ts', () => { dispose: () => {} } - provideLayerVisibility = true + fixture = TestBed.createComponent(NehubaViewerUnit) + fixture.componentInstance.nehubaViewer = nehubaViewerSpy + fixture.componentInstance['_nehubaReady'] = true }) it('> if provided obs does not emit, does not call manage layers', fakeAsync(() => { - const fixture = TestBed.createComponent(NehubaViewerUnit) - fixture.componentInstance.nehubaViewer = nehubaViewerSpy fixture.detectChanges() tick(320) expect(managedLayersSpy).not.toHaveBeenCalled() @@ -264,9 +190,7 @@ describe('> nehubaViewer.component.ts', () => { describe('> if provided obs does emit', () => { const setup = (emit = []) => { - const fixture = TestBed.createComponent(NehubaViewerUnit) setLayerVisibility$.next(emit) - fixture.componentInstance.nehubaViewer = nehubaViewerSpy fixture.detectChanges() tick(640) } @@ -311,11 +235,13 @@ describe('> nehubaViewer.component.ts', () => { let prvSetCMSpy: jasmine.Spy const setup = () => { - const fixture = TestBed.createComponent(NehubaViewerUnit) + fixture = TestBed.createComponent(NehubaViewerUnit) + fixture.componentInstance['_nehubaReady'] = true + /** * set nehubaViewer, since some methods check viewer is loaded */ - fixture.componentInstance.nehubaViewer = { + fixture.componentInstance.nehubaViewer = { ngviewer: {}, dispose: () => {} } @@ -323,9 +249,6 @@ describe('> nehubaViewer.component.ts', () => { prvSetCMSpy = spyOn<any>(fixture.componentInstance, 'setColorMap').and.callFake(() => {}) } - beforeEach(() => { - provideSetColorObs = true - }) describe('> obs does not emit', () => { beforeEach(fakeAsync(() => { setup() diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts index 8281c6565f4f6c717e8586877285781f631a1069..b1f70202ac24bfbe8375be62bcee549eb5132694 100644 --- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts +++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts @@ -1,12 +1,11 @@ -import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, Inject, Optional, ChangeDetectionStrategy } from "@angular/core"; -import { fromEvent, Subscription, ReplaySubject, BehaviorSubject, Observable, race, timer, Subject } from 'rxjs' -import { debounceTime, filter, map, scan, startWith, mapTo, switchMap, take, skip, tap, distinctUntilChanged } from "rxjs/operators"; +import { Component, ElementRef, EventEmitter, OnDestroy, Output, Inject, Optional } from "@angular/core"; +import { fromEvent, Subscription, BehaviorSubject, Observable, Subject, of, interval } from 'rxjs' +import { debounceTime, filter, map, scan, switchMap, take, distinctUntilChanged, debounce } from "rxjs/operators"; import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; -import { StateInterface as ViewerConfiguration } from "src/services/state/viewerConfig.store"; import { LoggingService } from "src/logging"; import { bufferUntil, getExportNehuba, getViewer, setNehubaViewer, switchMapWaitFor } from "src/util/fn"; -import { NEHUBA_INSTANCE_INJTKN, scanSliceViewRenderFn } from "../util"; -import { deserialiseParcRegionId, arrayOrderedEql } from 'common/util' +import { deserializeSegment, NEHUBA_INSTANCE_INJTKN } from "../util"; +import { arrayOrderedEql } from 'common/util' import { IMeshesToLoad, SET_MESHES_TO_LOAD } from "../constants"; import { IColorMap, SET_COLORMAP_OBS, SET_LAYER_VISIBILITY } from "../layerCtrl.service"; @@ -36,10 +35,6 @@ interface LayerLabelIndex { labelIndicies: number[] } -export interface INehubaLifecycleHook{ - onInit?: () => void -} - export const scanFn = (acc: LayerLabelIndex[], curr: LayerLabelIndex) => { const found = acc.find(layerLabelIndex => { return layerLabelIndex.layer.name === curr.layer.name @@ -62,17 +57,12 @@ export const scanFn = (acc: LayerLabelIndex[], curr: LayerLabelIndex) => { styleUrls : [ './nehubaViewer.style.css', ], - // OnPush seems to improve performance significantly - changeDetection: ChangeDetectionStrategy.OnPush }) -export class NehubaViewerUnit implements OnInit, OnDestroy { +export class NehubaViewerUnit implements OnDestroy { - private sliceviewLoading$: Observable<boolean> - public overrideShowLayers: string[] = [] - - public lifecycle: INehubaLifecycleHook + public ngIdSegmentsMap: Record<string, number[]> = {} public viewerPosInVoxel$ = new BehaviorSubject(null) public viewerPosInReal$ = new BehaviorSubject(null) @@ -83,6 +73,7 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { private subscriptions: Subscription[] = [] + private _nehubaReady = false @Output() public nehubaReady: EventEmitter<null> = new EventEmitter() @Output() public layersChanged: EventEmitter<null> = new EventEmitter() private layersChangedHandler: any @@ -90,7 +81,6 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { @Output() public mouseoverSegmentEmitter: EventEmitter<{ segmentId: number | null - segment: string | null layer: { name?: string url?: string @@ -98,15 +88,17 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { }> = new EventEmitter() @Output() public mouseoverLandmarkEmitter: EventEmitter<string> = new EventEmitter() @Output() public mouseoverUserlandmarkEmitter: EventEmitter<string> = new EventEmitter() - @Output() public regionSelectionEmitter: EventEmitter<{segment: number, layer: {name?: string, url?: string}}> = new EventEmitter() + @Output() public regionSelectionEmitter: EventEmitter<{ + segment: number + layer: { + name?: string + url?: string + }}> = new EventEmitter() @Output() public errorEmitter: EventEmitter<any> = new EventEmitter() - public auxilaryMeshIndices: number[] = [] /* only used to set initial navigation state */ public initNav: any - public initRegions: any[] - public initNiftiLayers: any[] = [] public config: any public nehubaViewer: any @@ -137,11 +129,6 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { public ondestroySubscriptions: Subscription[] = [] - private createNehubaPromiseRs: () => void - private createNehubaPromise = new Promise<void>(rs => { - this.createNehubaPromiseRs = rs - }) - public nehubaLoaded: boolean = false public landmarksLoaded: boolean = false @@ -176,54 +163,22 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { this.patchNG() this.loadNehuba() - this.layersChangedHandler = this.nehubaViewer.ngviewer.layerManager.layersChanged.add(() => this.layersChanged.emit(null)) - this.nehubaViewer.ngviewer.registerDisposer(this.layersChangedHandler) - }) - .then(() => { - // all mutation to this.nehubaViewer should await createNehubaPromise - this.createNehubaPromiseRs() + const viewer = this.nehubaViewer.ngviewer + this.layersChangedHandler = viewer.layerManager.layersChanged.add(() => { + this.layersChanged.emit(null) + const readiedLayerNames: string[] = viewer.layerManager.managedLayers.filter(l => l.layer).map(l => l.name) + for (const layerName in this.ngIdSegmentsMap) { + if (!readiedLayerNames.includes(layerName)) { + return + } + } + this._nehubaReady = true + this.nehubaReady.emit(null) + }) + viewer.registerDisposer(this.layersChangedHandler) }) .catch(e => this.errorEmitter.emit(e)) - /** - * TODO move to layerCtrl.service - */ - this.ondestroySubscriptions.push( - fromEvent(this.workerService.worker, 'message').pipe( - filter((message: any) => { - - if (!message) { - // this.log.error('worker response message is undefined', message) - return false - } - if (!message.data) { - // this.log.error('worker response message.data is undefined', message.data) - return false - } - if (message.data.type !== 'ASSEMBLED_LANDMARKS_VTK') { - /* worker responded with not assembled landmark, no need to act */ - return false - } - if (!message.data.url) { - /* file url needs to be defined */ - return false - } - return true - }), - debounceTime(100), - filter(e => this.templateId === e.data.template), - map(e => e.data.url), - ).subscribe(url => { - this.removeSpatialSearch3DLandmarks() - const _ = {} - _[NG_LANDMARK_LAYER_NAME] = { - type : 'mesh', - source : `vtk://${url}`, - shader : FRAGMENT_MAIN_WHITE, - } - this.loadLayer(_) - }), - ) /** * TODO move to layerCtrl.service @@ -289,7 +244,7 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { this.ondestroySubscriptions.push( this.setColormap$.pipe( switchMap(switchMapWaitFor({ - condition: () => !!(this.nehubaViewer?.ngviewer) + condition: () => this._nehubaReady })), debounceTime(160), ).subscribe(v => { @@ -311,7 +266,9 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { if (this.layerVis$) { this.ondestroySubscriptions.push( this.layerVis$.pipe( - switchMap(switchMapWaitFor({ condition: () => !!(this.nehubaViewer?.ngviewer) })), + switchMap(switchMapWaitFor({ + condition: () => this._nehubaReady + })), distinctUntilChanged(arrayOrderedEql), debounceTime(160), ).subscribe((layerNames: string[]) => { @@ -338,7 +295,14 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { if (this.segVis$) { this.ondestroySubscriptions.push( - this.segVis$.pipe().subscribe(val => { + this.segVis$.pipe( + switchMap( + switchMapWaitFor({ + condition: () => this._nehubaReady, + leading: true, + }) + ) + ).subscribe(val => { // null === hide all seg if (val === null) { this.hideAllSeg() @@ -361,7 +325,7 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { this.ondestroySubscriptions.push( this.layerCtrl$.pipe( bufferUntil(({ - condition: () => !!this.nehubaViewer?.ngviewer + condition: () => this._nehubaReady })) ).subscribe(messages => { for (const message of messages) { @@ -385,17 +349,47 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { this.setLayerTransparency(key, p.payload[key]) } } + if (message.type === "updateShader") { + const p = message as TNgLayerCtrl<'updateShader'> + for (const key in p.payload) { + this.setLayerShader(key, p.payload[key]) + } + } } }) ) } else { this.log.error(`NG_LAYER_CONTROL not provided`) } - } - public numMeshesToBeLoaded: number = 0 + if (this.injSetMeshesToLoad$) { + this.subscriptions.push( + this.injSetMeshesToLoad$.pipe( + scan(scanFn, []), + debounceTime(16), + debounce(() => this._nehubaReady + ? of(true) + : interval(160).pipe( + filter(() => this._nehubaReady), + take(1), + ) + ), + ).subscribe(layersLabelIndex => { + let totalMeshes = 0 + for (const layerLayerIndex of layersLabelIndex) { + const { layer, labelIndicies } = layerLayerIndex + totalMeshes += labelIndicies.length + this.nehubaViewer.setMeshesToLoad(labelIndicies, layer) + } + // TODO implement total mesh to be loaded and mesh loading UI + }), + ) + } else { + this.log.error(`SET_MESHES_TO_LOAD not provided`) + } + } - public applyPerformanceConfig({ gpuLimit }: Partial<ViewerConfiguration>) { + public applyGpuLimit(gpuLimit: number) { if (gpuLimit && this.nehubaViewer) { const limit = this.nehubaViewer.ngviewer.state.children.get('gpuMemoryLimit') if (limit && limit.restoreState) { @@ -404,15 +398,6 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { } } - /* required to check if correct landmarks are loaded */ - private _templateId: string - get templateId() { - return this._templateId - } - set templateId(id: string) { - this._templateId = id - } - public spatialLandmarkSelectionChanged(labels: number[]) { const getCondition = (label: number) => `if(label > ${label - 0.1} && label < ${label + 0.1} ){${FRAGMENT_EMIT_RED}}` const newShader = `void main(){ ${labels.map(getCondition).join('else ')}else {${FRAGMENT_EMIT_WHITE}} }` @@ -432,10 +417,6 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { } } - // TODO move region management to another service - - public multiNgIdsLabelIndexMap: Map<string, Map<number, any>> = new Map() - public navPosReal: [number, number, number] = [0, 0, 0] public navPosVoxel: [number, number, number] = [0, 0, 0] @@ -453,8 +434,6 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { this._multiNgIdColorMap = val } - private loadMeshes$: ReplaySubject<{labelIndicies: number[], layer: { name: string }}> = new ReplaySubject() - public mouseOverSegment: number | null public mouseOverLayer: {name: string, url: string}| null @@ -462,12 +441,8 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { ? this.exportNehuba.getNgHash() : null - public redraw() { - this.nehubaViewer.redraw() - } - public loadNehuba() { - this.nehubaViewer = this.exportNehuba.createNehubaViewer(this.config, (err) => { + this.nehubaViewer = this.exportNehuba.createNehubaViewer(this.config, (err: string) => { /* print in debug mode */ this.log.error(err) }) @@ -477,12 +452,9 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { * Then show the layers referenced in multiNgIdLabelIndexMap */ - this.redraw() - /* creation of the layout is done on next frame, hence the settimeout */ setTimeout(() => { getViewer().display.panels.forEach(patchSliceViewPanel) - this.nehubaReady.emit(null) }) this.newViewerInit() @@ -493,52 +465,6 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { this.onDestroyCb.push(() => setNehubaViewer(null)) } - public ngOnInit() { - this.sliceviewLoading$ = fromEvent(this.elementRef.nativeElement, 'sliceRenderEvent').pipe( - scan(scanSliceViewRenderFn, [ null, null, null ]), - map(arrOfFlags => arrOfFlags.some(flag => flag)), - startWith(true), - ) - - this.subscriptions.push( - (this.injSetMeshesToLoad$ || this.loadMeshes$).pipe( - scan(scanFn, []), - debounceTime(100), - switchMap(layerLabelIdx => - /** - * sometimes (e.g. when all slice views are minimised), sliceviewlaoding will not emit - * so if sliceviewloading does not emit another value (except the initial true value) - * force start loading of mesh - */ - race( - this.sliceviewLoading$.pipe( - skip(1) - ), - timer(500).pipe( - mapTo(false) - ) - ).pipe( - filter(flag => !flag), - take(1), - mapTo(layerLabelIdx), - ) - ), - ).subscribe(layersLabelIndex => { - let totalMeshes = 0 - for (const layerLayerIndex of layersLabelIndex) { - const { layer, labelIndicies } = layerLayerIndex - totalMeshes += labelIndicies.length - this.nehubaViewer.setMeshesToLoad(labelIndicies, layer) - } - // TODO implement total mesh to be loaded and mesh loading UI - this.numMeshesToBeLoaded = totalMeshes - }), - ) - - const { onInit } = this.lifecycle || {} - onInit && onInit.call(this) - } - public ngOnDestroy() { if (this.nehubaViewer$) { this.nehubaViewer$.next(null) @@ -579,10 +505,12 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { * The emitted value does not affect the region selection * the region selection is taken care of in nehubaContainer */ - const map = this.multiNgIdsLabelIndexMap.get(this.mouseOverLayer.name) - const region = map && map.get(this.mouseOverSegment) + if (arg === 'select') { - this.regionSelectionEmitter.emit({ segment: region, layer: this.mouseOverLayer }) + this.regionSelectionEmitter.emit({ + segment: this.mouseOverSegment, + layer: this.mouseOverLayer + }) } } @@ -662,23 +590,6 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { }) } - // pos in mm - public addSpatialSearch3DLandmarks(geometries: any[], scale?: number, type?: 'icosahedron') { - this.workerService.worker.postMessage({ - type : 'GET_LANDMARKS_VTK', - template : this.templateId, - scale: Math.min(...this.dim.map(v => v * NG_LANDMARK_CONSTANT)), - landmarks : geometries.map(geometry => - geometry === null - ? null - // gemoetry : [number,number,number] | [ [number,number,number][], [number,number,number][] ] - : isNaN(geometry[0]) - ? [geometry[0].map(triplets => triplets.map(coord => coord * 1e6)), geometry[1]] - : geometry.map(coord => coord * 1e6), - ), - }) - } - public setLayerVisibility(condition: {name: string|RegExp}, visible: boolean) { if (!this.nehubaViewer) { return false @@ -728,28 +639,33 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { } public hideAllSeg() { - if (!this.nehubaViewer) { return } - Array.from(this.multiNgIdsLabelIndexMap.keys()).forEach(ngId => { - - Array.from(this.multiNgIdsLabelIndexMap.get(ngId).keys()).forEach(idx => { + if (!this.nehubaViewer) return + for (const ngId in this.ngIdSegmentsMap) { + for (const idx of this.ngIdSegmentsMap[ngId]) { this.nehubaViewer.hideSegment(idx, { name: ngId, }) - }) + } + this.nehubaViewer.showSegment(0, { name: ngId, }) - }) + } } public showAllSeg() { if (!this.nehubaViewer) { return } - this.hideAllSeg() - Array.from(this.multiNgIdsLabelIndexMap.keys()).forEach(ngId => { + for (const ngId in this.ngIdSegmentsMap) { + for (const idx of this.ngIdSegmentsMap[ngId]) { + this.nehubaViewer.showSegment(idx, { + name: ngId, + }) + } + this.nehubaViewer.hideSegment(0, { name: ngId, }) - }) + } } public showSegs(array: (number|string)[]) { @@ -772,7 +688,7 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { const reduceFn: (acc: Map<string, number[]>, curr: string) => Map<string, number[]> = (acc, curr) => { const newMap = new Map(acc) - const { ngId, labelIndex } = deserialiseParcRegionId(curr) + const { ngId, label: labelIndex } = deserializeSegment(curr) const exist = newMap.get(ngId) if (!exist) { newMap.set(ngId, [Number(labelIndex)]) @@ -804,7 +720,7 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { }) } - private vec3(pos: [number, number, number]) { + private vec3(pos: number[]) { return this.exportNehuba.vec3.fromValues(...pos) } @@ -875,7 +791,20 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { private setLayerTransparency(layerName: string, alpha: number) { const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(layerName) if (!layer) return - layer.layer.displayState.objectAlpha.restoreState(alpha) + + /** + * for segmentation layer + */ + if (layer.layer.displayState) layer.layer.displayState.objectAlpha.restoreState(alpha) + /** + * for image layer + */ + if (layer.layer.opacity) layer.layer.opacity.restoreState(alpha) + } + + private setLayerShader(layerName: string, shader: string) { + const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(layerName) + if (layer?.layer?.fragmentMain) layer.layer.fragmentMain.restoreState(shader) } public setMeshTransparency(flag: boolean){ @@ -883,7 +812,7 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { /** * remove transparency from meshes in current layer(s) */ - for (const layerKey of this.multiNgIdsLabelIndexMap.keys()) { + for (const layerKey in this.ngIdSegmentsMap) { const layer = this.nehubaViewer.ngviewer.layerManager.getLayerByName(layerKey) if (layer) { layer.layer.displayState.objectAlpha.restoreState(flag ? 0.2 : 1.0) @@ -891,6 +820,10 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { } } + public redraw(){ + this.nehubaViewer.redraw() + } + private newViewerInit() { /* isn't this layer specific? */ @@ -906,24 +839,9 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { this.initNav = null } - if (this.initRegions && this.initRegions.length > 0) { - this.hideAllSeg() - this.showSegs(this.initRegions) - } - - if (this.initNiftiLayers.length > 0) { - this.initNiftiLayers.forEach(layer => this.loadLayer(layer)) - this.hideAllSeg() - } - this._s8$ = this.nehubaViewer.mouseOver.segment.subscribe(({segment: segmentId, layer }) => { - - const {name = 'unnamed'} = layer - const map = this.multiNgIdsLabelIndexMap.get(name) - const region = map && map.get(segmentId) this.mouseoverSegmentEmitter.emit({ layer, - segment: region, segmentId, }) }) @@ -1017,7 +935,6 @@ export class NehubaViewerUnit implements OnInit, OnDestroy { this._s$.forEach(_s$ => { if (_s$) { _s$.unsubscribe() } }) - } private setColorMap(map: Map<string, Map<number, {red: number, green: number, blue: number}>>) { @@ -1053,10 +970,10 @@ const patchSliceViewPanel = (sliceViewPanel: any) => { } export interface ViewerState { - orientation: [number, number, number, number] - perspectiveOrientation: [number, number, number, number] + orientation: number[] + perspectiveOrientation: number[] perspectiveZoom: number - position: [number, number, number] + position: number[] positionReal: boolean zoom: number } diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts index a73f96b59d85b83f00c9dd684257df7e7990ec81..df06e08ba9ffbe21ffe2ae10d10d70e2d2e57247 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.spec.ts @@ -7,28 +7,22 @@ import { NEVER, Subject } from "rxjs" import { ComponentsModule } from "src/components" import { ClickInterceptorService } from "src/glue" import { LayoutModule } from "src/layouts/layout.module" -import { PANELS } from "src/services/state/ngViewerState/constants" -import { ngViewerSelectorOctantRemoval, ngViewerSelectorPanelMode, ngViewerSelectorPanelOrder } from "src/services/state/ngViewerState/selectors" -import { uiStateMouseOverSegmentsSelector } from "src/services/state/uiState/selectors" -import { viewerStateSetSelectedRegions } from "src/services/state/viewerState.store.helper" -import { viewerStateCustomLandmarkSelector, viewerStateNavigationStateSelector, viewerStateSelectedRegionsSelector } from "src/services/state/viewerState/selectors" -import { Landmark2DModule } from "src/ui/nehubaContainer/2dLandmarks/module" import { QuickTourModule } from "src/ui/quickTour" import { AngularMaterialModule } from "src/sharedModules/angularMaterial.module" import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR, UtilModule } from "src/util" import { WindowResizeModule } from "src/util/windowResize" import { NehubaLayerControlService } from "../layerCtrl.service" -import { MaximisePanelButton } from "../maximisePanelButton/maximisePanelButton.component" import { NehubaMeshService } from "../mesh.service" import { NehubaViewerTouchDirective } from "../nehubaViewerInterface/nehubaViewerTouch.directive" import { selectorAuxMeshes } from "../store" -import { TouchSideClass } from "../touchSideClass.directive" import { NehubaGlueCmp } from "./nehubaViewerGlue.component" -import { HarnessLoader } from "@angular/cdk/testing" import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service" +import { userInterface, atlasSelection, atlasAppearance, annotation, userInteraction } from "src/state" +import { SapiAtlasModel, SAPIModule, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" +import { LayerCtrlEffects } from "../layerCtrl.service/layerCtrl.effects" +import { NEHUBA_INSTANCE_INJTKN } from "../util" import { RouterService } from "src/routerModule/router.service" - @Component({ selector: 'viewer-ctrl-component', template: '' @@ -55,28 +49,31 @@ class MockNehubaViewerContainerDirective{ describe('> nehubaViewerGlue.component.ts', () => { let mockStore: MockStore - let rootLoader: HarnessLoader let fixture: ComponentFixture<NehubaGlueCmp> + const selectedATPR$ = new Subject<{ + atlas: SapiAtlasModel, + parcellation: SapiParcellationModel, + template: SapiSpaceModel, + regions: SapiRegionModel[], + }>() beforeEach( async () => { await TestBed.configureTestingModule({ imports: [ CommonModule, AngularMaterialModule, - LayoutModule, - Landmark2DModule, + FormsModule, + ReactiveFormsModule, + QuickTourModule, ComponentsModule, UtilModule, WindowResizeModule, - FormsModule, - ReactiveFormsModule, - // NehubaModule, + LayoutModule, + SAPIModule, ], declarations: [ NehubaGlueCmp, - MaximisePanelButton, MockViewerCtrlCmp, - TouchSideClass, // TODO this may introduce a lot more dep MockNehubaViewerContainerDirective, @@ -87,11 +84,7 @@ describe('> nehubaViewerGlue.component.ts', () => { * TODO, figureout which dependency is selecting viewerState.parcellationSelected * then remove the inital state */ - provideMockStore({ - initialState: { - viewerState: {} - } - }), + provideMockStore(), { provide: CLICK_INTERCEPTOR_INJECTOR, useFactory: (clickIntService: ClickInterceptorService) => { @@ -110,6 +103,7 @@ describe('> nehubaViewerGlue.component.ts', () => { visibleLayer$: new Subject(), segmentVis$: new Subject(), ngLayersController$: new Subject(), + selectedATPR$ } },{ provide: RouterService, @@ -133,6 +127,15 @@ describe('> nehubaViewerGlue.component.ts', () => { } } } + },{ + provide: NEHUBA_INSTANCE_INJTKN, + useValue: NEVER + }, + { + provide: LayerCtrlEffects, + useValue: { + onATPDebounceNgLayers$: NEVER + } } ] }).compileComponents() @@ -140,13 +143,21 @@ describe('> nehubaViewerGlue.component.ts', () => { beforeEach(() => { mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(ngViewerSelectorPanelMode, PANELS.FOUR_PANEL) - mockStore.overrideSelector(ngViewerSelectorPanelOrder, '0123') - mockStore.overrideSelector(ngViewerSelectorOctantRemoval, true) - mockStore.overrideSelector(viewerStateCustomLandmarkSelector, []) - mockStore.overrideSelector(viewerStateSelectedRegionsSelector, []) - mockStore.overrideSelector(uiStateMouseOverSegmentsSelector, []) - mockStore.overrideSelector(viewerStateNavigationStateSelector, null) + mockStore.overrideSelector(userInterface.selectors.panelMode, "FOUR_PANEL") + mockStore.overrideSelector(userInterface.selectors.panelOrder, '0123') + mockStore.overrideSelector(atlasAppearance.selectors.octantRemoval, true) + + mockStore.overrideSelector(atlasSelection.selectors.selectedAtlas, null) + mockStore.overrideSelector(atlasSelection.selectors.selectedTemplate, null) + mockStore.overrideSelector(atlasSelection.selectors.selectedParcellation, null) + mockStore.overrideSelector(atlasSelection.selectors.selectedRegions, []) + mockStore.overrideSelector(atlasSelection.selectors.selectedParcAllRegions, []) + mockStore.overrideSelector(userInteraction.selectors.mousingOverRegions, []) + + mockStore.overrideSelector(atlasSelection.selectors.navigation, null) + mockStore.overrideSelector(atlasAppearance.selectors.showDelineation, true) + mockStore.overrideSelector(atlasAppearance.selectors.customLayers, []) + mockStore.overrideSelector(annotation.selectors.annotations, []) mockStore.overrideSelector(selectorAuxMeshes, []) }) @@ -192,7 +203,6 @@ describe('> nehubaViewerGlue.component.ts', () => { const testObj1 = 'hello world' beforeEach(() => { fallbackSpy = spyOn(clickIntServ, 'fallback') - mockStore.overrideSelector(uiStateMouseOverSegmentsSelector, [testObj1, testObj0] as any) TestBed.createComponent(NehubaGlueCmp) clickIntServ.callRegFns(null) }) @@ -221,7 +231,6 @@ describe('> nehubaViewerGlue.component.ts', () => { } beforeEach(() => { fallbackSpy = spyOn(clickIntServ, 'fallback') - mockStore.overrideSelector(uiStateMouseOverSegmentsSelector, [testObj0, testObj1, testObj2] as any) }) afterEach(() => { @@ -231,11 +240,6 @@ describe('> nehubaViewerGlue.component.ts', () => { TestBed.createComponent(NehubaGlueCmp) clickIntServ.callRegFns(null) const { segment } = testObj1 - expect(dispatchSpy).toHaveBeenCalledWith( - viewerStateSetSelectedRegions({ - selectRegions: [segment] - } as any) - ) }) it('> fallback called (does not intercept)', () => { TestBed.createComponent(NehubaGlueCmp) @@ -246,14 +250,14 @@ describe('> nehubaViewerGlue.component.ts', () => { }) describe('> handleFileDrop', () => { - let addNgLayerSpy: jasmine.Spy - let removeNgLayersSpy: jasmine.Spy + let dispatchSpy: jasmine.Spy let workerSendMessageSpy: jasmine.Spy let dummyFile1: File let dummyFile2: File let input: File[] beforeEach(() => { + dispatchSpy = spyOn(mockStore, 'dispatch') dummyFile1 = (() => { const bl: any = new Blob([], { type: 'text' }) bl.name = 'filename1.txt' @@ -271,13 +275,6 @@ describe('> nehubaViewerGlue.component.ts', () => { fixture = TestBed.createComponent(NehubaGlueCmp) fixture.detectChanges() - addNgLayerSpy = spyOn(fixture.componentInstance['layerCtrlService'], 'addNgLayer').and.callFake(() => { - - }) - removeNgLayersSpy = spyOn(fixture.componentInstance['layerCtrlService'], 'removeNgLayers').and.callFake(() => { - - }) - workerSendMessageSpy = spyOn(fixture.componentInstance['worker'], 'sendMessage').and.callFake(async () => { return { result: { @@ -287,8 +284,7 @@ describe('> nehubaViewerGlue.component.ts', () => { }) }) afterEach(() => { - addNgLayerSpy.calls.reset() - removeNgLayersSpy.calls.reset() + dispatchSpy.calls.reset() workerSendMessageSpy.calls.reset() }) @@ -311,8 +307,7 @@ describe('> nehubaViewerGlue.component.ts', () => { }) it('> should not call addnglayer', () => { - expect(removeNgLayersSpy).not.toHaveBeenCalled() - expect(addNgLayerSpy).not.toHaveBeenCalled() + expect(dispatchSpy).not.toHaveBeenCalled() }) // TODO having a difficult time getting snackbar harness @@ -346,15 +341,28 @@ describe('> nehubaViewerGlue.component.ts', () => { }) it('> should call addNgLayer', () => { - expect(removeNgLayersSpy).not.toHaveBeenCalled() - expect(addNgLayerSpy).toHaveBeenCalledTimes(1) + expect(dispatchSpy).toHaveBeenCalledTimes(1) + const arg = dispatchSpy.calls.argsFor(0) + expect(arg.length).toEqual(1) + expect(arg[0].type).toBe(atlasAppearance.actions.addCustomLayer.type) }) it('> on repeated input, both remove nglayer and remove ng layer called', async () => { const cmp = fixture.componentInstance await cmp.handleFileDrop(input) - expect(removeNgLayersSpy).toHaveBeenCalledTimes(1) - expect(addNgLayerSpy).toHaveBeenCalledTimes(2) + expect(dispatchSpy).toHaveBeenCalledTimes(3) + + const arg0 = dispatchSpy.calls.argsFor(0) + expect(arg0.length).toEqual(1) + expect(arg0[0].type).toBe(atlasAppearance.actions.addCustomLayer.type) + + const arg1 = dispatchSpy.calls.argsFor(1) + expect(arg1.length).toEqual(1) + expect(arg1[0].type).toBe(atlasAppearance.actions.removeCustomLayer.type) + + const arg2 = dispatchSpy.calls.argsFor(2) + expect(arg2.length).toEqual(1) + expect(arg2[0].type).toBe(atlasAppearance.actions.addCustomLayer.type) }) }) }) diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts index f8df7f66789021d41fe5f0435e6b24ae83a27c39..0c7b084eb8dab41764d88d913578e8eeb843478b 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts @@ -1,29 +1,14 @@ -import { AfterViewInit, Component, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, Optional, Output, SimpleChanges, TemplateRef, ViewChild } from "@angular/core"; +import { AfterViewInit, ChangeDetectionStrategy, Component, EventEmitter, Inject, OnDestroy, Optional, Output, TemplateRef, ViewChild } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { asyncScheduler, combineLatest, fromEvent, merge, NEVER, Observable, of, Subject } from "rxjs"; -import { ngViewerActionCycleViews, ngViewerActionToggleMax } from "src/services/state/ngViewerState/actions"; +import { Subscription } from "rxjs"; import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; -import { uiStateMouseOverSegmentsSelector } from "src/services/state/uiState/selectors"; -import { debounceTime, distinctUntilChanged, filter, map, mapTo, scan, shareReplay, startWith, switchMap, switchMapTo, take, tap, throttleTime } from "rxjs/operators"; -import { viewerStateAddUserLandmarks, viewerStateChangeNavigation, viewerStateMouseOverCustomLandmark, viewerStateSelectRegionWithIdDeprecated, viewerStateSetSelectedRegions, viewreStateRemoveUserLandmarks } from "src/services/state/viewerState/actions"; -import { ngViewerSelectorPanelOrder, ngViewerSelectorPanelMode } from "src/services/state/ngViewerState/selectors"; -import { viewerStateCustomLandmarkSelector, viewerStateNavigationStateSelector } from "src/services/state/viewerState/selectors"; -import { serialiseParcellationRegion } from 'common/util' -import { ARIA_LABELS, IDS, QUICKTOUR_DESC } from 'common/constants' -import { PANELS } from "src/services/state/ngViewerState/constants"; -import { LoggingService } from "src/logging"; - -import { getMultiNgIdsRegionsLabelIndexMap, SET_MESHES_TO_LOAD } from "../constants"; +import { distinctUntilChanged, startWith } from "rxjs/operators"; +import { ARIA_LABELS } from 'common/constants' import { EnumViewerEvt, IViewer, TViewerEvent } from "../../viewer.interface"; -import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; import { NehubaViewerContainerDirective, TMouseoverEvent } from "../nehubaViewerInterface/nehubaViewerInterface.directive"; -import { cvtNavigationObjToNehubaConfig, getFourPanel, getHorizontalOneThree, getSinglePanel, getVerticalOneThree, scanSliceViewRenderFn, takeOnePipe } from "../util"; -import { API_SERVICE_SET_VIEWER_HANDLE_TOKEN, TSetViewerHandle } from "src/atlasViewer/atlasViewer.apiService.service"; -import { MouseHoverDirective } from "src/mouseoverModule"; import { NehubaMeshService } from "../mesh.service"; -import { IQuickTourData } from "src/ui/quickTour/constrants"; -import { NehubaLayerControlService, IColorMap, SET_COLORMAP_OBS, SET_LAYER_VISIBILITY } from "../layerCtrl.service"; -import { getExportNehuba, getUuid, switchMapWaitFor } from "src/util/fn"; +import { NehubaLayerControlService, SET_COLORMAP_OBS, SET_LAYER_VISIBILITY } from "../layerCtrl.service"; +import { getExportNehuba, getUuid } from "src/util/fn"; import { INavObj } from "../navigation.service"; import { NG_LAYER_CONTROL, SET_SEGMENT_VISIBILITY } from "../layerCtrl.service/layerCtrl.util"; import { MatSnackBar } from "@angular/material/snack-bar"; @@ -31,6 +16,10 @@ import { getShader } from "src/util/constants"; import { EnumColorMapName } from "src/util/colorMaps"; import { MatDialog } from "@angular/material/dialog"; import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; +import { SapiRegionModel } from "src/atlasComponents/sapi"; +import { NehubaConfig, getParcNgId, getRegionLabelIndex } from "../config.service"; +import { SET_MESHES_TO_LOAD } from "../constants"; +import { annotation, atlasAppearance, atlasSelection, userInteraction } from "src/state"; export const INVALID_FILE_INPUT = `Exactly one (1) nifti file is required!` @@ -68,241 +57,123 @@ export const INVALID_FILE_INPUT = `Exactly one (1) nifti file is required!` useFactory: (layerCtrl: NehubaLayerControlService) => layerCtrl.ngLayersController$, deps: [ NehubaLayerControlService ] }, - NehubaLayerControlService - ] + NehubaLayerControlService, + NehubaMeshService, + ], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, AfterViewInit { +export class NehubaGlueCmp implements IViewer<'nehuba'>, OnDestroy, AfterViewInit { @ViewChild('layerCtrlTmpl', { read: TemplateRef }) layerCtrlTmpl: TemplateRef<any> public ARIA_LABELS = ARIA_LABELS - public IDS = IDS - - private currentPanelMode: PANELS @ViewChild(NehubaViewerContainerDirective, { static: true }) public nehubaContainerDirective: NehubaViewerContainerDirective - @ViewChild(MouseHoverDirective, { static: true }) - private mouseoverDirective: MouseHoverDirective - - public viewerLoaded: boolean = false - - private onhoverSegments = [] + private onhoverSegments: SapiRegionModel[] = [] private onDestroyCb: (() => void)[] = [] - private viewerUnit: NehubaViewerUnit - private multiNgIdsRegionsLabelIndexMap: Map<string, Map<number, any>> - - @Input() - public selectedParcellation: any - - @Input() - public selectedTemplate: any - - private navigation: any - - private newViewer$ = new Subject() - - public showPerpsectiveScreen$: Observable<string> - public sliceViewLoadingMain$: Observable<[boolean, boolean, boolean]> - private sliceRenderEvent$: Observable<CustomEvent> - public perspectiveViewLoading$: Observable<string|null> - public hoveredPanelIndices$: Observable<number> - private viewPanelWeakMap = new WeakMap<HTMLElement, number>() - public viewPanels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement] = [null, null, null, null] - private findPanelIndex = (panel: HTMLElement) => this.viewPanelWeakMap.get(panel) - public nanometersToOffsetPixelsFn: Array<(...arg) => any> = [] - - public quickTourSliceViewSlide: IQuickTourData = { - order: 1, - description: QUICKTOUR_DESC.SLICE_VIEW, - } - - public quickTour3dViewSlide: IQuickTourData = { - order: 2, - description: QUICKTOUR_DESC.PERSPECTIVE_VIEW, - } + private multiNgIdsRegionsLabelIndexMap = new Map<string, Map<number, SapiRegionModel>>() - public quickTourIconsSlide: IQuickTourData = { - order: 3, - description: QUICKTOUR_DESC.VIEW_ICONS, - } + public nehubaConfig: NehubaConfig - public customLandmarks$: Observable<any> = this.store$.pipe( - select(viewerStateCustomLandmarkSelector), - map(lms => lms.map(lm => ({ - ...lm, - geometry: { - position: lm.position - } - }))), + public customLandmarks$ = this.store$.pipe( + select(annotation.selectors.annotations), ) - public filterCustomLandmark(lm: any){ - return !!lm['showInSliceView'] - } - - public panelOrder$ = this.store$.pipe( - select(ngViewerSelectorPanelOrder), - distinctUntilChanged(), - shareReplay(1), - ) - - ngOnChanges(sc: SimpleChanges){ + private nehubaContainerSub: Subscription[] = [] + private setupNehubaEvRelay() { + while (this.nehubaContainerSub.length > 0) this.nehubaContainerSub.pop().unsubscribe() + + if (!this.nehubaContainerDirective) return const { - selectedParcellation, - selectedTemplate - } = sc - if (selectedTemplate) { - if (selectedTemplate?.currentValue?.['@id'] !== selectedTemplate?.previousValue?.['@id']) { - - if (selectedTemplate?.previousValue) { - this.unloadTmpl(selectedTemplate?.previousValue) - } - if (selectedTemplate?.currentValue?.['@id']) { - this.loadTmpl(selectedTemplate.currentValue, selectedParcellation.currentValue) - } - } - }else if (selectedParcellation && selectedParcellation.currentValue !== selectedParcellation.previousValue) { - this.loadParc(selectedParcellation.currentValue) - } - } - - ngAfterViewInit(){ - this.setQuickTourPos() - - const { - mouseOverSegments = NEVER, - navigationEmitter = NEVER, - mousePosEmitter = NEVER, - } = this.nehubaContainerDirective || {} - const sub = combineLatest([ mouseOverSegments, navigationEmitter, mousePosEmitter, - ]).pipe( - throttleTime(16, asyncScheduler, { trailing: true }) - ).subscribe(([ seg, nav, mouse ]: [ TMouseoverEvent [], INavObj, { real: number[], voxel: number[] } ]) => { - this.viewerEvent.emit({ - type: EnumViewerEvt.VIEWER_CTX, - data: { - viewerType: 'nehuba', - payload: { - nav, - mouse, - nehuba: seg.map(v => { - return { - layerName: v.layer.name, - labelIndices: [ Number(v.segmentId) ] - } - }) - } - } - }) - }) - this.onDestroyCb.push( - () => sub.unsubscribe() - ) - } + } = this.nehubaContainerDirective - ngOnDestroy() { - while (this.onDestroyCb.length) this.onDestroyCb.pop()() - } + this.nehubaContainerSub.push( - private loadParc(parcellation: any) { - /** - * parcellaiton may be undefined - */ - if ( !(parcellation && parcellation.regions)) { - return - } + mouseOverSegments.pipe( + startWith(null as TMouseoverEvent[]) + ).subscribe(seg => { + this.viewerEvent.emit({ + type: EnumViewerEvt.VIEWER_CTX, + data: { + viewerType: 'nehuba', + payload: { + nehuba: seg && seg.map(v => { + return { + layerName: v.layer.name, + labelIndices: [ Number(v.segmentId) ], + regions: (() => { + const map = this.multiNgIdsRegionsLabelIndexMap.get(v.layer.name) + if (!map) return [] + return [map.get(Number(v.segmentId))] + })() + } + }) + } + } + }) + }), - this.multiNgIdsRegionsLabelIndexMap = getMultiNgIdsRegionsLabelIndexMap(parcellation) + navigationEmitter.pipe( + startWith(null as INavObj) + ).subscribe(nav => { + this.viewerEvent.emit({ + type: EnumViewerEvt.VIEWER_CTX, + data: { + viewerType: 'nehuba', + payload: { + nav + } + } + }) + }), - this.viewerUnit.multiNgIdsLabelIndexMap = this.multiNgIdsRegionsLabelIndexMap - this.viewerUnit.auxilaryMeshIndices = parcellation.auxillaryMeshIndices || [] + mousePosEmitter.pipe( + startWith(null as { + voxel: number[] + real: number[] + }) + ).subscribe(mouse => { + this.viewerEvent.emit({ + type: EnumViewerEvt.VIEWER_CTX, + data: { + viewerType: 'nehuba', + payload: { + mouse + } + } + }) + }) + ) + this.onDestroyCb.push( + () => { + if (this.nehubaContainerSub) { + while(this.nehubaContainerSub.length > 0) this.nehubaContainerSub.pop().unsubscribe() + } + } + ) } - private unloadTmpl(tmpl: any) { - /** - * clear existing container - */ - this.viewerUnit = null - this.nehubaContainerDirective.clear() + ngAfterViewInit(): void { + this.setupNehubaEvRelay() + } - /* on selecting of new template, remove additional nglayers */ - const baseLayerNames = Object.keys(tmpl.nehubaConfig.dataset.initialNgState.layers) - this.layerCtrlService.removeNgLayers(baseLayerNames) + ngOnDestroy(): void { + while (this.onDestroyCb.length) this.onDestroyCb.pop()() } - private async loadTmpl(_template: any, parcellation: any) { - if (!_template) return + private disposeViewer() { /** - * recalcuate zoom + * clear existing container */ - const template = (() => { - - const deepCopiedState = JSON.parse(JSON.stringify(_template)) - const initialNgState = deepCopiedState.nehubaConfig.dataset.initialNgState - - if (!initialNgState || !this.navigation) { - return deepCopiedState - } - const overwritingInitState = this.navigation - ? cvtNavigationObjToNehubaConfig(this.navigation, initialNgState) - : {} - - deepCopiedState.nehubaConfig.dataset.initialNgState = { - ...initialNgState, - ...overwritingInitState, - } - return deepCopiedState - })() - - this.nehubaContainerDirective.createNehubaInstance(template) - this.viewerUnit = this.nehubaContainerDirective.nehubaViewerInstance - this.sliceRenderEvent$.pipe( - takeOnePipe() - ).subscribe(ev => { - - for (const idx of [0, 1, 2]) { - const e = ev[idx] as CustomEvent - const el = e.target as HTMLElement - this.viewPanelWeakMap.set(el, idx) - this.viewPanels[idx] = el - this.nanometersToOffsetPixelsFn[idx] = e.detail.nanometersToOffsetPixels - } - }) - const foundParcellation = parcellation - && template?.parcellations?.find(p => parcellation.name === p.name) - this.loadParc(foundParcellation || template.parcellations[0]) - - const nehubaConfig = template.nehubaConfig - const initialSpec = nehubaConfig.dataset.initialNgState - const {layers} = initialSpec - - const dispatchLayers = Object.keys(layers).map(key => { - const layer = { - name : key, - source : layers[key].source, - mixability : layers[key].type === 'image' - ? 'base' - : 'mixable', - visible : typeof layers[key].visible === 'undefined' - ? true - : layers[key].visible, - transform : typeof layers[key].transform === 'undefined' - ? null - : layers[key].transform, - } - return layer - }) - - this.layerCtrlService.addNgLayer(dispatchLayers) - this.newViewer$.next(true) + this.nehubaContainerDirective && this.nehubaContainerDirective.clear() } @Output() @@ -310,14 +181,11 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A constructor( private store$: Store<any>, - private el: ElementRef, - private log: LoggingService, private snackbar: MatSnackBar, private dialog: MatDialog, private worker: AtlasWorkerService, + private layerCtrlService: NehubaLayerControlService, @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor, - @Optional() @Inject(API_SERVICE_SET_VIEWER_HANDLE_TOKEN) setViewerHandle: TSetViewerHandle, - @Optional() private layerCtrlService: NehubaLayerControlService, ){ /** @@ -330,346 +198,47 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A this.onDestroyCb.push(() => deregister(selOnhoverRegion)) } + const onATPClear = this.store$.pipe( + atlasSelection.fromRootStore.distinctATP() + ).subscribe(this.disposeViewer.bind(this)) + this.onDestroyCb.push(() => onATPClear.unsubscribe()) + /** - * on layout change + * subscribe to ngIdtolblIdxToRegion */ - const redrawLayoutSub = combineLatest([ - this.store$.pipe( - select(ngViewerSelectorPanelMode), - distinctUntilChanged(), - shareReplay(1), - ), - this.panelOrder$, - ]).pipe( - switchMap(this.waitForNehuba.bind(this)) - ).subscribe(([mode, panelOrder]) => { - - this.currentPanelMode = mode - - const viewPanels = panelOrder.split('').map(v => Number(v)).map(idx => this.viewPanels[idx]) as [HTMLElement, HTMLElement, HTMLElement, HTMLElement] - - /** - * TODO smarter with event stream - */ - if (!viewPanels.every(v => !!v)) { - this.log.error(`on relayout, not every view panel is populated. This should not occur!`) - return - } - - switch (mode) { - case PANELS.H_ONE_THREE: { - const element = this.removeExistingPanels() - const newEl = getHorizontalOneThree(viewPanels) - element.appendChild(newEl) - break; - } - case PANELS.V_ONE_THREE: { - const element = this.removeExistingPanels() - const newEl = getVerticalOneThree(viewPanels) - element.appendChild(newEl) - break; - } - case PANELS.FOUR_PANEL: { - const element = this.removeExistingPanels() - const newEl = getFourPanel(viewPanels) - element.appendChild(newEl) - break; - } - case PANELS.SINGLE_PANEL: { - const element = this.removeExistingPanels() - const newEl = getSinglePanel(viewPanels) - element.appendChild(newEl) - break; - } - default: - } - for (const panel of viewPanels) { - (panel as HTMLElement).classList.add('neuroglancer-panel') + const ngIdSub = this.layerCtrlService.selectedATPR$.subscribe(({ atlas, parcellation, template, regions }) => { + this.multiNgIdsRegionsLabelIndexMap.clear() + for (const r of regions) { + const ngId = getParcNgId(atlas, template, parcellation, r) + if (!ngId) continue + if (!this.multiNgIdsRegionsLabelIndexMap.has(ngId)) { + this.multiNgIdsRegionsLabelIndexMap.set(ngId, new Map()) + } + const labelIndex = getRegionLabelIndex(atlas, template, parcellation, r) + if (!labelIndex) continue + this.multiNgIdsRegionsLabelIndexMap.get(ngId).set(labelIndex, r) } - - // TODO needed to redraw? - // see https://trello.com/c/oJOnlc6v/60-enlarge-panel-allow-user-rearrange-panel-position - // further investigaation required - this.nehubaContainerDirective.nehubaViewerInstance.redraw() }) - this.onDestroyCb.push(() => redrawLayoutSub.unsubscribe()) + this.onDestroyCb.push(() => ngIdSub.unsubscribe()) /** * on hover segment */ const onhovSegSub = this.store$.pipe( - select(uiStateMouseOverSegmentsSelector), + select(userInteraction.selectors.mousingOverRegions), distinctUntilChanged(), ).subscribe(arr => { - const segments = arr.map(({ segment }) => segment).filter(v => !!v) - this.onhoverSegments = segments + this.onhoverSegments = arr }) this.onDestroyCb.push(() => onhovSegSub.unsubscribe()) - - const perspectiveRenderEvSub = this.newViewer$.pipe( - switchMapTo(fromEvent<CustomEvent>(this.el.nativeElement, 'perpspectiveRenderEvent').pipe( - take(1) - )) - ).subscribe(ev => { - const perspPanel = ev.target as HTMLElement - this.viewPanels[3] = perspPanel - this.viewPanelWeakMap.set(perspPanel, 3) - }) - this.onDestroyCb.push(() => perspectiveRenderEvSub.unsubscribe()) - - this.sliceRenderEvent$ = fromEvent<CustomEvent>(this.el.nativeElement, 'sliceRenderEvent') - this.sliceViewLoadingMain$ = this.sliceRenderEvent$.pipe( - scan(scanSliceViewRenderFn, [null, null, null]), - startWith([true, true, true] as [boolean, boolean, boolean]), - shareReplay(1), - ) - - this.perspectiveViewLoading$ = fromEvent(this.el.nativeElement, 'perpspectiveRenderEvent').pipe( - filter((event: CustomEvent) => event?.detail?.lastLoadedMeshId ), - map(event => { - - /** - * TODO dig into event detail to see if the exact mesh loaded - */ - const { meshesLoaded, meshFragmentsLoaded: _meshFragmentsLoaded, lastLoadedMeshId: _lastLoadedMeshId } = (event as any).detail - return meshesLoaded >= this.nehubaContainerDirective.nehubaViewerInstance.numMeshesToBeLoaded - ? null - : 'Loading meshes ...' - }), - distinctUntilChanged() - ) - - this.showPerpsectiveScreen$ = this.newViewer$.pipe( - switchMapTo(this.sliceRenderEvent$.pipe( - scan((acc, curr) => { - - /** - * if at any point, all chunks have been loaded, always return loaded state - */ - if (acc.every(v => v === 0)) return [0, 0, 0] - const { detail = {}, target } = curr || {} - const { missingChunks = -1, missingImageChunks = -1 } = detail - const idx = this.findPanelIndex(target as HTMLElement) - const returnAcc = [...acc] - if (idx >= 0) { - returnAcc[idx] = missingChunks + missingImageChunks - } - return returnAcc - }, [-1, -1, -1]), - map(arr => { - let sum = 0 - let uncertain = false - for (const num of arr) { - if (num < 0) { - uncertain = true - } else { - sum += num - } - } - return sum > 0 - ? `Loading ${sum}${uncertain ? '+' : ''} chunks ...` - : null - }), - distinctUntilChanged(), - startWith('Loading ...'), - throttleTime(100, asyncScheduler, { leading: true, trailing: true }), - shareReplay(1), - )) - ) - - this.hoveredPanelIndices$ = fromEvent(this.el.nativeElement, 'mouseover').pipe( - switchMap((ev: MouseEvent) => merge( - of(this.findPanelIndex(ev.target as HTMLElement)), - fromEvent(this.el.nativeElement, 'mouseout').pipe( - mapTo(null), - ), - )), - debounceTime(20), - shareReplay(1), - ) - - const setupViewerApiSub = this.newViewer$.pipe( - tap(() => { - setViewerHandle && setViewerHandle(null) - }), - switchMap(this.waitForNehuba.bind(this)) - ).subscribe(() => { - setViewerHandle && setViewerHandle({ - setNavigationLoc : (coord, realSpace?) => this.nehubaContainerDirective.nehubaViewerInstance.setNavigationState({ - position : coord, - positionReal : typeof realSpace !== 'undefined' ? realSpace : true, - }), - /* TODO introduce animation */ - moveToNavigationLoc : (coord, _realSpace?) => { - this.store$.dispatch( - viewerStateChangeNavigation({ - navigation: { - position: coord, - animation: {}, - } - }) - ) - }, - setNavigationOri : (quat) => this.nehubaContainerDirective.nehubaViewerInstance.setNavigationState({ - orientation : quat, - }), - /* TODO introduce animation */ - moveToNavigationOri : (quat) => this.nehubaContainerDirective.nehubaViewerInstance.setNavigationState({ - orientation : quat, - }), - showSegment : (_labelIndex) => { - /** - * TODO reenable with updated select_regions api - */ - this.log.warn(`showSegment is temporarily disabled`) - - // if(!this.selectedRegionIndexSet.has(labelIndex)) - // this.store.dispatch({ - // type : SELECT_REGIONS, - // selectRegions : [labelIndex, ...this.selectedRegionIndexSet] - // }) - }, - add3DLandmarks : landmarks => { - // TODO check uniqueness of ID - if (!landmarks.every(l => !!l.id)) { - throw new Error('every landmarks needs to be identified with the id field') - } - if (!landmarks.every(l => !!l.position)) { - throw new Error('every landmarks needs to have position defined') - } - if (!landmarks.every(l => l.position.constructor === Array) || !landmarks.every(l => l.position.every(v => !isNaN(v))) || !landmarks.every(l => l.position.length == 3)) { - throw new Error('position needs to be a length 3 tuple of numbers ') - } - - this.store$.dispatch(viewerStateAddUserLandmarks({ - landmarks - })) - }, - remove3DLandmarks : landmarkIds => { - this.store$.dispatch(viewreStateRemoveUserLandmarks({ - payload: { landmarkIds } - })) - }, - hideSegment : (_labelIndex) => { - /** - * TODO reenable with updated select_regions api - */ - this.log.warn(`hideSegment is temporarily disabled`) - - // if(this.selectedRegionIndexSet.has(labelIndex)){ - // this.store.dispatch({ - // type :SELECT_REGIONS, - // selectRegions : [...this.selectedRegionIndexSet].filter(num=>num!==labelIndex) - // }) - // } - }, - showAllSegments : () => { - const selectRegionIds = [] - this.multiNgIdsRegionsLabelIndexMap.forEach((map, ngId) => { - Array.from(map.keys()).forEach(labelIndex => { - selectRegionIds.push(serialiseParcellationRegion({ ngId, labelIndex })) - }) - }) - this.store$.dispatch(viewerStateSelectRegionWithIdDeprecated({ - selectRegionIds - })) - }, - hideAllSegments : () => { - this.store$.dispatch(viewerStateSelectRegionWithIdDeprecated({ - selectRegionIds: [] - })) - }, - getLayersSegmentColourMap: () => { - if (!this.layerCtrlService) { - throw new Error(`layerCtrlService not injected. Cannot call getLayersSegmentColourMap`) - } - const newMainMap = new Map() - for (const key in this.layerCtrlService.activeColorMap) { - const obj = this.layerCtrlService.activeColorMap[key] - const m = new Map() - newMainMap.set(key, m) - for (const labelIndex in obj) { - m.set(Number(labelIndex), obj[labelIndex]) - } - } - return newMainMap - }, - applyLayersColourMap: (map) => { - if (!this.layerCtrlService) { - throw new Error(`layerCtrlService not injected. Cannot call getLayersSegmentColourMap`) - } - const obj: IColorMap = {} - for (const [ key, value ] of map.entries()) { - const cmap = obj[key] = {} - for (const [ labelIdx, rgb ] of value.entries()) { - cmap[Number(labelIdx)] = rgb - } - } - this.layerCtrlService.overwriteColorMap$.next(obj) - }, - /** - * TODO go via layerCtrl.service - */ - loadLayer : (layerObj) => this.nehubaContainerDirective.nehubaViewerInstance.loadLayer(layerObj), - removeLayer : (condition) => this.nehubaContainerDirective.nehubaViewerInstance.removeLayer(condition), - setLayerVisibility : (condition, visible) => this.nehubaContainerDirective.nehubaViewerInstance.setLayerVisibility(condition, visible), - mouseEvent : merge( - fromEvent(this.el.nativeElement, 'click').pipe( - map((ev: MouseEvent) => ({eventName : 'click', event: ev})), - ), - fromEvent(this.el.nativeElement, 'mousemove').pipe( - map((ev: MouseEvent) => ({eventName : 'mousemove', event: ev})), - ), - /** - * neuroglancer prevents propagation, so use capture instead - */ - fromEvent(this.el.nativeElement, 'mousedown', { capture: true }).pipe( - map((event: MouseEvent) => { - return { - eventName: 'mousedown', - event - } - }) - ), - fromEvent(this.el.nativeElement, 'mouseup').pipe( - map((ev: MouseEvent) => ({eventName : 'mouseup', event: ev})), - ), - ) , - mouseOverNehuba : of(null).pipe( - tap(() => console.warn('mouseOverNehuba observable is becoming deprecated. use mouseOverNehubaLayers instead.')), - ), - mouseOverNehubaLayers: this.mouseoverDirective.currentOnHoverObs$.pipe( - map(({ segments }) => segments) - ), - mouseOverNehubaUI: this.mouseoverDirective.currentOnHoverObs$.pipe( - map(({annotation, landmark, segments, userLandmark: customLandmark }) => ({annotation, segments, landmark, customLandmark })), - shareReplay(1), - ), - getNgHash : this.nehubaContainerDirective.nehubaViewerInstance.getNgHash, - }) - }) - this.onDestroyCb.push(() => setupViewerApiSub.unsubscribe()) - - // listen to navigation change from store - const navSub = this.store$.pipe( - select(viewerStateNavigationStateSelector) - ).subscribe(nav => this.navigation = nav) - this.onDestroyCb.push(() => navSub.unsubscribe()) } - handleCycleViewEvent(){ - if (this.currentPanelMode !== PANELS.SINGLE_PANEL) return - this.store$.dispatch( - ngViewerActionCycleViews() - ) - } - handleViewerLoadedEvent(flag: boolean) { + handleViewerLoadedEvent(flag: boolean): void { this.viewerEvent.emit({ type: EnumViewerEvt.VIEWERLOADED, data: flag }) - this.viewerLoaded = flag } private selectHoveredRegion(_ev: any): boolean{ @@ -679,48 +248,13 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A const trueOnhoverSegments = this.onhoverSegments && this.onhoverSegments.filter(v => typeof v === 'object') if (!trueOnhoverSegments || (trueOnhoverSegments.length === 0)) return true this.store$.dispatch( - viewerStateSetSelectedRegions({ - selectRegions: trueOnhoverSegments.slice(0, 1) + atlasSelection.actions.selectRegion({ + region: trueOnhoverSegments[0] }) ) return true } - private waitForNehuba = switchMapWaitFor({ - condition: () => !!(this.nehubaContainerDirective?.isReady()) - }) - - public toggleMaximiseMinimise(index: number) { - this.store$.dispatch(ngViewerActionToggleMax({ - payload: { index } - })) - } - - public zoomNgView(panelIndex: number, factor: number) { - const ngviewer = this.nehubaContainerDirective?.nehubaViewerInstance?.nehubaViewer?.ngviewer - if (!ngviewer) throw new Error(`ngviewer not defined!`) - - /** - * panelIndex < 3 === slice view - */ - if (panelIndex < 3) { - /** - * factor > 1 === zoom out - */ - ngviewer.navigationState.zoomBy(factor) - } else { - ngviewer.perspectiveNavigationState.zoomBy(factor) - } - } - - private removeExistingPanels() { - const element = this.nehubaContainerDirective.nehubaViewerInstance.nehubaViewer.ngviewer.layout.container.componentValue.element as HTMLElement - while (element.childElementCount > 0) { - element.removeChild(element.firstElementChild) - } - return element - } - private droppedLayerNames: { layerName: string resourceUrl: string @@ -728,11 +262,16 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A private dismissAllAddedLayers(){ while (this.droppedLayerNames.length) { const { resourceUrl, layerName } = this.droppedLayerNames.pop() - this.layerCtrlService.removeNgLayers([ layerName ]) + this.store$.dispatch( + atlasAppearance.actions.removeCustomLayer({ + id: layerName + }) + ) + URL.revokeObjectURL(resourceUrl) } } - public async handleFileDrop(files: File[]){ + public async handleFileDrop(files: File[]): Promise<void> { if (files.length !== 1) { this.snackbar.open(INVALID_FILE_INPUT, 'Dismiss', { duration: 5000 @@ -759,7 +298,7 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A } try { - const { result, ...other } = await this.worker.sendMessage({ + const { result } = await this.worker.sendMessage({ method: 'PROCESS_NIFTI', param: { nifti: outbuf @@ -774,17 +313,21 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A layerName: randomUuid, resourceUrl: url }) - this.layerCtrlService.addNgLayer([{ - name: randomUuid, - mixability: 'mixable', - source: `nifti://${url}`, - shader: getShader({ - colormap: EnumColorMapName.MAGMA, - lowThreshold: meta.min || 0, - highThreshold: meta.max || 1 - }) - }]) + this.store$.dispatch( + atlasAppearance.actions.addCustomLayer({ + customLayer: { + id: randomUuid, + source: `nifti://${url}`, + shader: getShader({ + colormap: EnumColorMapName.MAGMA, + lowThreshold: meta.min || 0, + highThreshold: meta.max || 1 + }), + clType: 'customlayer/nglayer' + } + }) + ) this.dialog.open( this.layerCtrlTmpl, { @@ -817,65 +360,4 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnChanges, OnDestroy, A }) } } - - - public returnTruePos(quadrant: number, data: any) { - const pos = quadrant > 2 - ? [0, 0, 0] - : this.nanometersToOffsetPixelsFn && this.nanometersToOffsetPixelsFn[quadrant] - ? this.nanometersToOffsetPixelsFn[quadrant](data.geometry.position.map(n => n * 1e6)) - : [0, 0, 0] - return pos - } - - public getPositionX(quadrant: number, data: any) { - return this.returnTruePos(quadrant, data)[0] - } - public getPositionY(quadrant: number, data: any) { - return this.returnTruePos(quadrant, data)[1] - } - public getPositionZ(quadrant: number, data: any) { - return this.returnTruePos(quadrant, data)[2] - } - - public handleMouseEnterCustomLandmark(lm) { - this.store$.dispatch( - viewerStateMouseOverCustomLandmark({ - payload: { userLandmark: lm } - }) - ) - } - - public handleMouseLeaveCustomLandmark(_lm) { - this.store$.dispatch( - viewerStateMouseOverCustomLandmark({ - payload: { userLandmark: null } - }) - ) - } - - public quickTourOverwritingPos = { - 'dialog': { - left: '0px', - top: '0px', - }, - 'arrow': { - left: '0px', - top: '0px', - } - } - - setQuickTourPos(){ - const { innerWidth, innerHeight } = window - this.quickTourOverwritingPos = { - 'dialog': { - left: `${innerWidth / 2}px`, - top: `${innerHeight / 2}px`, - }, - 'arrow': { - left: `${innerWidth / 2 - 48}px`, - top: `${innerHeight / 2 - 48}px`, - } - } - } } diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.style.css b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.style.css index c859c65655905d25cd4f65fe332d3c3567cc6868..5d085f9333a8ddd06516b3bdebb5f43c6fb80640 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.style.css +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.style.css @@ -1,25 +1,4 @@ -.opacity-crossfade -{ - transition: opacity 170ms ease-in-out, - transform 250ms ease-in-out; -} - -.opacity-crossfade -{ - - opacity: 0.0; - pointer-events: none; -} - -.opacity-crossfade.onHover, -.opacity-crossfade:hover, -:host-context([ismobile="true"]) .opacity-crossfade.always-show-touchdevice -{ - opacity: 1.0 !important; - pointer-events: all !important; -} - .screen-overlay { background-color: rgba(255, 255, 255, 0.7); @@ -33,9 +12,23 @@ .nehuba-viewer-container-parent { z-index: 1; + display: block; + width: 100%; + height: 100%; } current-layout { z-index: 2; } + +nehuba-layout-overlay +{ + display: block; + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + z-index: 2; +} diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html index 104c5add581a8bcb216d1afe8d15b42002657938..1b24745daa5b9d1234601f736888a472fa7a4705 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html @@ -1,198 +1,19 @@ -<div class="d-block w-100 h-100 nehuba-viewer-container-parent" - (touchmove)="$event.preventDefault()" - (drag-drop-file)="handleFileDrop($event)" +<div class="nehuba-viewer-container-parent" iav-viewer-touch-interface - [iav-viewer-touch-interface-v-panels]="viewPanels" - [iav-viewer-touch-interface-vp-to-data]="iavContainer?.viewportToDatas" - [iav-viewer-touch-interface-ngviewer]="iavContainer?.nehubaViewerInstance?.nehubaViewer?.ngviewer" - [iav-viewer-touch-interface-nehuba-config]="selectedTemplate?.nehubaConfig"> + (touchmove)="$event.preventDefault()" + (drag-drop-file)="handleFileDrop($event)"> - <div class="d-block" + <div class="sxplr-d-block" iav-nehuba-viewer-container #iavContainer="iavNehubaViewerContainer" - iav-mouse-hover - [iav-key-listener]="[{ type: 'keydown', key: ' ', target: 'document', capture: true }]" - (iav-key-event)="handleCycleViewEvent()" (iavNehubaViewerContainerViewerLoading)="handleViewerLoadedEvent($event)"> </div> </div> -<current-layout *ngIf="viewerLoaded" class="position-absolute w-100 h-100 d-block pe-none top-0 left-0"> - <div class="w-100 h-100 position-relative" cell-i - iav-window-resize - [iav-window-resize-time]="64" - (iav-window-resize-event)="setQuickTourPos()" - quick-tour - [quick-tour-description]="quickTourSliceViewSlide.description" - [quick-tour-order]="quickTourSliceViewSlide.order" - [quick-tour-overwrite-arrow]="sliceViewArrow" - [quick-tour-overwrite-position]="quickTourOverwritingPos"> - <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getProperty : 0 | parseAsNumber }"></ng-content> - </div> - <div class="w-100 h-100 position-relative" cell-ii> - <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getProperty : 1 | parseAsNumber }"></ng-content> - </div> - <div class="w-100 h-100 position-relative" cell-iii> - <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getProperty : 2 | parseAsNumber }"></ng-content> - </div> - <div class="w-100 h-100 position-relative" cell-iv - quick-tour - [quick-tour-description]="quickTour3dViewSlide.description" - [quick-tour-order]="quickTour3dViewSlide.order"> - <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getProperty : 3 | parseAsNumber }"></ng-content> - </div> -</current-layout> - -<!-- slice view overlay tmpl --> -<ng-template #ngPanelOverlayTmpl let-panelIndex="panelIndex"> - - <!-- perspective view tmpl --> - <ng-template #overlayPerspectiveTmpl> - <layout-floating-container landmarkContainer> - - <div class="d-flex flex-column justify-content-center align-items-center w-100 h-100 position-absolute opacity-crossfade screen-overlay pe-none" - [ngClass]="{onHover: !!(showPerpsectiveScreen$ | async)}" - [attr.id]="IDS.MESH_LOADING_STATUS" - role="status"> - - <spinner-cmp *ngIf="showPerpsectiveScreen$ | async"> - </spinner-cmp> - - <mat-list> - <mat-list-item> - {{ showPerpsectiveScreen$ | async }} - </mat-list-item> - </mat-list> - </div> - - <!-- mesh loading is still weird --> - <!-- if the precomputed server does not have the necessary fragment file, then the numberws will not collate --> - <div *ngIf="false && (perspectiveViewLoading$ | async)" class="loadingIndicator"> - <spinner-cmp></spinner-cmp> - - <div *ngIf="false" perspectiveLoadingText> - {{ perspectiveViewLoading$ | async }} - </div> - </div> - </layout-floating-container> - </ng-template> - - <iav-layout-fourcorners class="w-100 h-100 d-block"> - <layout-floating-container *ngIf="panelIndex < 3; else overlayPerspectiveTmpl" - class="overflow-hidden" - iavLayoutFourCornersContent> - <!-- TODO add landmarks here --> - - - <!-- customLandmarks --> - <!-- only show landmarks in slice views --> - <landmark-2d-flat-cmp *ngFor="let lm of (customLandmarks$ | async | filterArray : filterCustomLandmark)" - (mouseenter)="handleMouseEnterCustomLandmark(lm)" - (mouseleave)="handleMouseLeaveCustomLandmark(lm)" - [color]="lm.color || [255, 255, 255]" - [positionX]="getPositionX(panelIndex, lm)" - [positionY]="getPositionY(panelIndex, lm)" - [positionZ]="getPositionZ(panelIndex, lm)"> - - </landmark-2d-flat-cmp> - </layout-floating-container> - - <!-- panel controller --> - <div iavLayoutFourCornersBottomRight class="position-relative"> - - <ng-container *ngTemplateOutlet="panelCtrlTmpl; context: { - panelIndex: panelIndex, - visible: (hoveredPanelIndices$ | async) === panelIndex - }"> - </ng-container> - - <div *ngIf="(sliceViewLoadingMain$ | async)[panelIndex]" - class="position-absolute bottom-0 right-0"> - <spinner-cmp></spinner-cmp> - </div> - </div> - </iav-layout-fourcorners> - -</ng-template> - -<!-- panel control template --> -<ng-template - #panelCtrlTmpl - let-panelIndex="panelIndex" - let-visible="visible"> - - <div class="ws-no-wrap opacity-crossfade always-show-touchdevice pe-all overlay-btn-container" - [ngClass]="{ onHover: visible }" - [attr.data-viewer-controller-visible]="visible" - [attr.data-viewer-controller-index]="panelIndex"> - - <div class="position-absolute w-100 h-100 pe-none" - *ngIf="panelIndex === 1" - quick-tour - [quick-tour-description]="quickTourIconsSlide.description" - [quick-tour-order]="quickTourIconsSlide.order"> - </div> - - <!-- perspective specific control --> - <ng-container *ngIf="panelIndex === 3"> - - <button mat-icon-button color="primary" - [matMenuTriggerFor]="viewerCtrlMenu"> - <i class="fas fa-cog"></i> - </button> - - </ng-container> - - <!-- factor < 1.0 === zoom in --> - <button mat-icon-button color="primary" - (click)="zoomNgView(panelIndex, 0.9)" - [attr.aria-label]="ARIA_LABELS.ZOOM_IN"> - <i class="fas fa-search-plus"></i> - </button> - - <!-- factor > 1.0 === zoom out --> - <button mat-icon-button color="primary" - (click)="zoomNgView(panelIndex, 1.1)" - [attr.aria-label]="ARIA_LABELS.ZOOM_OUT"> - <i class="fas fa-search-minus"></i> - </button> - - <maximise-panel-button - (click)="toggleMaximiseMinimise(panelIndex)" - [touch-side-class]="panelIndex"> - </maximise-panel-button> - </div> - -</ng-template> - -<!-- viewer ctrl --> -<mat-menu #viewerCtrlMenu> - <!-- NB must not lazy load. key listener needs to work even when component is not yet rendered --> - <!-- stop propagation is needed, or else click will result in dismiss of menu --> - <viewer-ctrl-component class="d-block m-2 ml-3 mr-3" - (click)="$event.stopPropagation()"> - </viewer-ctrl-component> -</mat-menu> - -<ng-template #sliceViewArrow> - <svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg"> - <path id="quarter_circle" d="M22.6151 96.5C22.6151 96.5 18.5 84.1266 18.5 76.5C18.5001 62 18.1151 59.5 22.6151 47C27.115 34.5 39.3315 27.7229 47.5 25C56.5 22 63 22.5 72.5 24C83.1615 25.6834 83.5 26 91 29" /> - <g id="arrow_top_left"> - <path id="arrow_stem" d="M37 40C35.5882 38.5882 17.6863 20.6863 12 15" /> - <path id="arrow_head" d="M6 24C6.38926 21.7912 6.68496 18.3286 6.71205 16.0803C6.73751 13.9665 6.632 13.6135 6.52632 11.5C6.46368 10.2469 6.52632 11.5 6 8C11 10 9.71916 9.74382 11 9.99999C13.5 10.5 13.743 10.7451 17 11C20 11.2348 21.1276 11 22 11" stroke-linecap="round" stroke-linejoin="round"/> - </g> - <g id="arrow_left"> - <path id="arrow_stem_2" d="M29.4229 78.5771C27.1573 78.5771 18.3177 78.5771 9.19238 78.5771" /> - <path id="arrow_head_2" d="M13.3137 89.6274C12.0271 87.7903 9.78778 85.1328 8.2171 83.5238C6.74048 82.0112 6.41626 81.8362 4.84703 80.4164C3.91668 79.5747 4.84703 80.4164 2 78.3137C6.94975 76.1924 5.86291 76.9169 6.94974 76.1924C9.07106 74.7782 9.41624 74.7797 11.8995 72.6569C14.1868 70.7016 14.8181 69.7382 15.435 69.1213" stroke-linecap="round" stroke-linejoin="round"/> - </g> - <g id="arrow_top"> - <path id="arrow_stem_3" d="M77.0057 32.0057C77.0057 30.3124 77.0057 16.2488 77.0057 9.42862" /> - <path id="arrow_head_3" d="M68.4342 11.1429C69.9189 10.1032 72.0665 8.29351 73.3667 7.02421C74.5891 5.83091 74.7305 5.5689 75.8779 4.30076C76.5581 3.54892 75.8779 4.30076 77.5771 2C79.2914 6.00002 78.7059 5.12172 79.2915 6.00002C80.4343 7.71431 80.4331 7.99326 82.1486 10C83.7287 11.8485 84.5072 12.3587 85.0058 12.8572" stroke-linecap="round" stroke-linejoin="round"/> - </g> - </svg> -</ng-template> +<nehuba-layout-overlay></nehuba-layout-overlay> +<!-- user dropped NIFTI overlay --> <ng-template #layerCtrlTmpl let-data> <div class="grid grid-col-3"> @@ -218,7 +39,7 @@ </button> <div *ngIf="data.moreInfoFlag" - class="iv-custom-comp darker-bg overflow-hidden grid-wide-3"> + class="sxplr-custom-cmp darker-bg overflow-hidden grid-wide-3"> <ng-layer-tune advanced-control="true" [ngLayerName]="data.layerName" diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts index 84a54b7e24da734f57683c6981203d4516dea27a..9b7a7b09d88e0ad801477b90e13f1c4a5f6e60bb 100644 --- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts +++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.spec.ts @@ -1,27 +1,26 @@ import { Component } from "@angular/core" -import { TestBed, async, ComponentFixture, fakeAsync, tick } from "@angular/core/testing" +import { TestBed, ComponentFixture, fakeAsync, tick } from "@angular/core/testing" import { By } from "@angular/platform-browser" -import { BrowserDynamicTestingModule } from "@angular/platform-browser-dynamic/testing" import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState/selectors" import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component" import { NehubaViewerContainerDirective } from "./nehubaViewerInterface.directive" -import { viewerStateSelectorNavigation, viewerStateStandAloneVolumes } from "src/services/state/viewerState/selectors"; -import { Subject } from "rxjs" -import { ngViewerActionNehubaReady } from "src/services/state/ngViewerState/actions" -import { viewerStateMouseOverCustomLandmarkInPerspectiveView } from "src/services/state/viewerState/actions" -import { selectViewerConfigAnimationFlag } from "src/services/state/viewerConfig/selectors" +import { NEVER, of, pipe, Subject } from "rxjs" +import { userPreference, atlasSelection, atlasAppearance } from "src/state" +import { NehubaNavigationService } from "../navigation.service" +import { LayerCtrlEffects } from "../layerCtrl.service/layerCtrl.effects" +import { mapTo } from "rxjs/operators" describe('> nehubaViewerInterface.directive.ts', () => { + let distinctATPSpy: jasmine.Spy describe('> NehubaViewerContainerDirective', () => { - @Component({ template: '' }) class DummyCmp{} - beforeEach(async(() => { - TestBed.configureTestingModule({ + beforeEach(async () => { + distinctATPSpy = spyOn(atlasSelection.fromRootStore, 'distinctATP') + await TestBed.configureTestingModule({ imports: [ ], @@ -31,7 +30,20 @@ describe('> nehubaViewerInterface.directive.ts', () => { NehubaViewerUnit, ], providers: [ - provideMockStore({ initialState: {} }) + provideMockStore(), + { + provide: NehubaNavigationService, + useValue: { + viewerNav$: NEVER, + storeNav: null + } + }, + { + provide: LayerCtrlEffects, + useValue: { + onATPDebounceNgLayers$: of({ parcNgLayers: {} }) + } + } ] }).overrideComponent(DummyCmp, { set: { @@ -42,14 +54,26 @@ describe('> nehubaViewerInterface.directive.ts', () => { } }).compileComponents() - })) + distinctATPSpy.and.returnValue( + pipe( + mapTo({ + atlas: null, + parcellation: null, + template: null + }) + ) + ) - beforeEach(() => { const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(ngViewerSelectorOctantRemoval, true) - mockStore.overrideSelector(viewerStateStandAloneVolumes, []) - mockStore.overrideSelector(viewerStateSelectorNavigation, null) - mockStore.overrideSelector(selectViewerConfigAnimationFlag, false) + // mockStore.overrideSelector(atlasSelection.selectors.selectedAtlas, null) + // mockStore.overrideSelector(atlasSelection.selectors.selectedTemplate, null) + // mockStore.overrideSelector(atlasSelection.selectors.selectedParcellation, null) + + mockStore.overrideSelector(atlasAppearance.selectors.customLayers, []) + mockStore.overrideSelector(atlasAppearance.selectors.octantRemoval, true) + mockStore.overrideSelector(atlasSelection.selectors.standaloneVolumes, []) + mockStore.overrideSelector(atlasSelection.selectors.navigation, null) + mockStore.overrideSelector(userPreference.selectors.useAnimation, false) }) it('> can be inited', () => { @@ -89,11 +113,17 @@ describe('> nehubaViewerInterface.directive.ts', () => { destroy: jasmine.createSpy('destroy') } - beforeEach(() => { + const gpuLimit = 5e8 + beforeEach(fakeAsync(() => { + const mockStore = TestBed.inject(MockStore) + mockStore.overrideSelector(userPreference.selectors.gpuLimit, gpuLimit) + fixture = TestBed.createComponent(DummyCmp) const directive = fixture.debugElement.query( By.directive(NehubaViewerContainerDirective) ) + + tick(300) directiveInstance = directive.injector.get(NehubaViewerContainerDirective) @@ -102,29 +132,27 @@ describe('> nehubaViewerInterface.directive.ts', () => { // casting return value to any is not perfect, but since only 2 methods and 1 property is used, it's a quick way // rather than allow component to be created elCreateComponentSpy = spyOn(directiveInstance['el'], 'createComponent').and.returnValue(spyComRef as any) - }) + })) describe('> on createNehubaInstance called', () => { - const template = {} - const lifecycle = {} - it('> method el.clear gets called before el.createComponent', () => { - directiveInstance.createNehubaInstance(template, lifecycle) + const nehubaConfig = { + dataset: { + initialNgState: { + + } + } + } + it('> method el.clear gets called before el.createComponent', async () => { + await directiveInstance.createNehubaInstance(nehubaConfig) expect(elClearSpy).toHaveBeenCalledBefore(elCreateComponentSpy) }) - it('> if viewerConfig has gpuLimit, gpuMemoryLimit will be in initialNgSTate', () => { - template['nehubaConfig'] = { - dataset: { - initialNgState: {} - } - } - directiveInstance['viewerConfig'] = { - gpuLimit: 5e8 - } - directiveInstance.createNehubaInstance(template, lifecycle) + it('> if viewerConfig has gpuLimit, gpuMemoryLimit will be in initialNgSTate', async () => { + + await directiveInstance.createNehubaInstance(nehubaConfig) expect( directiveInstance.nehubaViewerInstance?.config?.dataset?.initialNgState?.gpuMemoryLimit - ).toEqual(5e8) + ).toEqual(gpuLimit) expect( directiveInstance.nehubaViewerInstance?.config?.dataset?.initialNgState?.gpuLimit ).toBeFalsy() @@ -133,16 +161,7 @@ describe('> nehubaViewerInterface.directive.ts', () => { describe('> on clear called', () => { it('> dispatches nehubaReady: false action', () => { - const mockStore = TestBed.inject(MockStore) - const mockStoreDispatchSpy = spyOn(mockStore, 'dispatch') - directiveInstance.clear() - expect( - mockStoreDispatchSpy - ).toHaveBeenCalledWith( - ngViewerActionNehubaReady({ - nehubaReady: false - }) - ) + }) it('> iavNehubaViewerContainerViewerLoading emits false', () => { @@ -209,82 +228,25 @@ describe('> nehubaViewerInterface.directive.ts', () => { }) it('> single null emits null', fakeAsync(() => { - directiveInstance.createNehubaInstance(template, lifecycle) - spyNehubaViewerInstance.mouseoverUserlandmarkEmitter.next(null) - - tick(200) - expect(dispatchSpy).toHaveBeenCalledWith( - viewerStateMouseOverCustomLandmarkInPerspectiveView({ - payload: { label: null } - }) - ) })) it('> single value emits value', fakeAsync(() => { - directiveInstance.createNehubaInstance(template, lifecycle) - spyNehubaViewerInstance.mouseoverUserlandmarkEmitter.next("24") - - tick(200) - expect(dispatchSpy).toHaveBeenCalledWith( - viewerStateMouseOverCustomLandmarkInPerspectiveView({ - payload: { label: "24" } - }) - ) })) describe('> double value in 140ms emits last value', () => { it('> null - 24 emits 24', fakeAsync(() => { - directiveInstance.createNehubaInstance(template, lifecycle) - spyNehubaViewerInstance.mouseoverUserlandmarkEmitter.next(null) - spyNehubaViewerInstance.mouseoverUserlandmarkEmitter.next("24") - - tick(200) - expect(dispatchSpy).toHaveBeenCalledWith( - viewerStateMouseOverCustomLandmarkInPerspectiveView({ - payload: { label: "24" } - }) - ) })) it('> 24 - null emits null', fakeAsync(() => { - directiveInstance.createNehubaInstance(template, lifecycle) - spyNehubaViewerInstance.mouseoverUserlandmarkEmitter.next("24") - spyNehubaViewerInstance.mouseoverUserlandmarkEmitter.next(null) - - tick(200) - expect(dispatchSpy).toHaveBeenCalledWith( - viewerStateMouseOverCustomLandmarkInPerspectiveView({ - payload: { label: null } - }) - ) + })) }) it('> single value outside 140 ms emits separately', fakeAsync(() => { - directiveInstance.createNehubaInstance(template, lifecycle) - spyNehubaViewerInstance.mouseoverUserlandmarkEmitter.next(null) - tick(200) - spyNehubaViewerInstance.mouseoverUserlandmarkEmitter.next("24") - - tick(200) - expect( - dispatchSpy.calls.allArgs() - ).toEqual([ - [ - viewerStateMouseOverCustomLandmarkInPerspectiveView({ - payload: { label: null } - }) - ], - [ - viewerStateMouseOverCustomLandmarkInPerspectiveView({ - payload: { label: "24" } - }) - ] - ]) })) }) }) diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts index 1b6fb599e73e9808098fe45872860672a26b5742..0b7f185aa8d4530a561d779f45274a9a3b13cff7 100644 --- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts +++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerInterface.directive.ts @@ -1,89 +1,20 @@ -import { Directive, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, ComponentRef, OnInit, OnDestroy, Output, EventEmitter, Optional } from "@angular/core"; -import { NehubaViewerUnit, INehubaLifecycleHook } from "../nehubaViewer/nehubaViewer.component"; +import { Directive, ViewContainerRef, ComponentRef, OnDestroy, Output, EventEmitter, Optional, ChangeDetectorRef, ComponentFactoryResolver, ComponentFactory } from "@angular/core"; +import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; import { Store, select } from "@ngrx/store"; -import { Subscription, Observable, fromEvent, asyncScheduler, combineLatest } from "rxjs"; -import { distinctUntilChanged, filter, debounceTime, scan, map, throttleTime, switchMapTo } from "rxjs/operators"; -import { takeOnePipe } from "../util"; -import { ngViewerActionNehubaReady } from "src/services/state/ngViewerState/actions"; -import { viewerStateMouseOverCustomLandmarkInPerspectiveView, viewerStateNehubaLayerchanged } from "src/services/state/viewerState/actions"; -import { viewerStateStandAloneVolumes } from "src/services/state/viewerState/selectors"; -import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState/selectors"; +import { Subscription, Observable, asyncScheduler, combineLatest } from "rxjs"; +import { distinctUntilChanged, filter, debounceTime, scan, map, throttleTime, switchMap, take } from "rxjs/operators"; +import { serializeSegment } from "../util"; import { LoggingService } from "src/logging"; -import { uiActionMouseoverLandmark, uiActionMouseoverSegments } from "src/services/state/uiState/actions"; -import { IViewerConfigState } from "src/services/state/viewerConfig.store.helper"; import { arrayOfPrimitiveEqual } from 'src/util/fn' import { INavObj, NehubaNavigationService } from "../navigation.service"; +import { NehubaConfig, defaultNehubaConfig, getNehubaConfig } from "../config.service"; +import { atlasAppearance, atlasSelection, userPreference } from "src/state"; +import { SapiAtlasModel, SapiParcellationModel, SapiSpaceModel } from "src/atlasComponents/sapi"; +import { NgLayerCustomLayer } from "src/state/atlasAppearance"; +import { arrayEqual } from "src/util/array"; +import { cvtNavigationObjToNehubaConfig } from "../config.service/util"; +import { LayerCtrlEffects } from "../layerCtrl.service/layerCtrl.effects"; -const defaultNehubaConfig = { - "configName": "", - "globals": { - "hideNullImageValues": true, - "useNehubaLayout": { - "keepDefaultLayouts": false - }, - "useNehubaMeshLayer": true, - "rightClickWithCtrlGlobal": false, - "zoomWithoutCtrlGlobal": false, - "useCustomSegmentColors": true - }, - "zoomWithoutCtrl": true, - "hideNeuroglancerUI": true, - "rightClickWithCtrl": true, - "rotateAtViewCentre": true, - "enableMeshLoadingControl": true, - "zoomAtViewCentre": true, - "restrictUserNavigation": true, - "disableSegmentSelection": false, - "dataset": { - "imageBackground": [ - 1, - 1, - 1, - 1 - ], - "initialNgState": { - "showDefaultAnnotations": false, - "layers": {}, - } - }, - "layout": { - "views": "hbp-neuro", - "planarSlicesBackground": [ - 1, - 1, - 1, - 1 - ], - "useNehubaPerspective": { - "enableShiftDrag": false, - "doNotRestrictUserNavigation": false, - "perspectiveSlicesBackground": [ - 1, - 1, - 1, - 1 - ], - "perspectiveBackground": [ - 1, - 1, - 1, - 1 - ], - "mesh": { - "backFaceColor": [ - 1, - 1, - 1, - 1 - ], - "removeBasedOnNavigation": true, - "flipRemovedOctant": true - }, - "hideImages": false, - "waitForMesh": false, - } - } -} const determineProtocol = (url: string) => { const re = /^([a-z0-9_-]{0,}):\/\//.exec(url) @@ -206,9 +137,7 @@ const accumulatorFn: ( exportAs: 'iavNehubaViewerContainer', providers: [ NehubaNavigationService ] }) -export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ - - public viewportToDatas: [any, any, any] = [null, null, null] +export class NehubaViewerContainerDirective implements OnDestroy{ @Output('iav-nehuba-viewer-container-mouseover') public mouseOverSegments = new EventEmitter<TMouseoverEvent[]>() @@ -222,51 +151,77 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ @Output() public iavNehubaViewerContainerViewerLoading: EventEmitter<boolean> = new EventEmitter() - private nehubaViewerFactory: ComponentFactory<NehubaViewerUnit> + private componentFactory: ComponentFactory<NehubaViewerUnit> private cr: ComponentRef<NehubaViewerUnit> + private navigation: atlasSelection.AtlasSelectionState['navigation'] constructor( private el: ViewContainerRef, - private cfr: ComponentFactoryResolver, private store$: Store<any>, private navService: NehubaNavigationService, + private effect: LayerCtrlEffects, + private cdr: ChangeDetectorRef, + cfr: ComponentFactoryResolver, @Optional() private log: LoggingService, ){ - this.nehubaViewerFactory = this.cfr.resolveComponentFactory(NehubaViewerUnit) - - this.viewerPerformanceConfig$ = this.store$.pipe( - select('viewerConfigState'), - /** - * TODO: this is only a bandaid fix. Technically, we should also implement - * logic to take the previously set config to apply oninit - */ - distinctUntilChanged(), - ) - - this.nehubaViewerPerspectiveOctantRemoval$ = this.store$.pipe( - select(ngViewerSelectorOctantRemoval), - ) - } + this.componentFactory = cfr.resolveComponentFactory(NehubaViewerUnit) + this.cdr.detach() - private nehubaViewerPerspectiveOctantRemoval$: Observable<boolean> - - private viewerPerformanceConfig$: Observable<IViewerConfigState> - private viewerConfig: Partial<IViewerConfigState> = {} - - private nehubaViewerSubscriptions: Subscription[] = [] - private subscriptions: Subscription[] = [] - - ngOnInit(){ this.subscriptions.push( this.nehubaViewerPerspectiveOctantRemoval$.pipe( distinctUntilChanged() ).subscribe(flag =>{ this.toggleOctantRemoval(flag) - }) - ) + }), + this.store$.pipe( + atlasSelection.fromRootStore.distinctATP(), + debounceTime(16), + switchMap((ATP: { atlas: SapiAtlasModel, parcellation: SapiParcellationModel, template: SapiSpaceModel }) => this.store$.pipe( + select(atlasAppearance.selectors.customLayers), + debounceTime(16), + map(cl => cl.filter(l => l.clType === "baselayer/nglayer") as NgLayerCustomLayer[]), + distinctUntilChanged(arrayEqual((oi, ni) => oi.id === ni.id)), + filter(layers => layers.length > 0), + map(ngBaseLayers => { + return { + ATP, + ngBaseLayers + } + }) + )) + ).subscribe(async ({ ATP, ngBaseLayers }) => { + const config = getNehubaConfig(ATP.template) + for (const baseLayer of ngBaseLayers) { + config.dataset.initialNgState.layers[baseLayer.id] = baseLayer + } + + const overwritingInitState = this.navigation + ? cvtNavigationObjToNehubaConfig(this.navigation, config.dataset.initialNgState) + : {} + + config.dataset.initialNgState = { + ...config.dataset.initialNgState, + ...overwritingInitState, + } + + const { + parcNgLayers, + } = await this.effect.onATPDebounceNgLayers$.pipe( + take(1) + ).toPromise() + + await this.createNehubaInstance(config) + + const ngIdSegmentsMap: Record<string, number[]> = {} + + for (const key in parcNgLayers) { + ngIdSegmentsMap[key] = parcNgLayers[key].labelIndicies + } + + this.nehubaViewerInstance.ngIdSegmentsMap = ngIdSegmentsMap + }), - this.subscriptions.push( this.store$.pipe( - select(viewerStateStandAloneVolumes), + select(atlasSelection.selectors.standaloneVolumes), filter(v => v && Array.isArray(v) && v.length > 0), distinctUntilChanged(arrayOfPrimitiveEqual) ).subscribe(async volumes => { @@ -282,33 +237,52 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ // TODO catch error } } - function onInit() { - this.overrideShowLayers = forceShowLayerNames - } - this.createNehubaInstance({ nehubaConfig: copiedNehubaConfig }, { onInit }) + await this.createNehubaInstance(copiedNehubaConfig) }), - this.viewerPerformanceConfig$.pipe( - debounceTime(200) - ).subscribe(config => { - this.viewerConfig = config + this.gpuLimit$.pipe( + debounceTime(160), + ).subscribe(limit => { + this.gpuLimit = limit if (this.nehubaViewerInstance && this.nehubaViewerInstance.nehubaViewer) { - this.nehubaViewerInstance.applyPerformanceConfig(config) + this.nehubaViewerInstance.applyGpuLimit(limit) } }), this.navService.viewerNav$.subscribe(v => { this.navigationEmitter.emit(v) - }) + }), + this.store$.pipe( + select(atlasSelection.selectors.navigation) + ).subscribe(nav => this.navigation = nav) ) } - ngOnDestroy(){ + private nehubaViewerPerspectiveOctantRemoval$ = this.store$.pipe( + select(atlasAppearance.selectors.octantRemoval), + ) + + private gpuLimit$: Observable<number> = this.store$.pipe( + select(userPreference.selectors.gpuLimit) + ) + private gpuLimit: number = null + + private nehubaViewerSubscriptions: Subscription[] = [] + private subscriptions: Subscription[] = [] + + redraw(): void{ + this.nehubaViewerInstance.redraw() + } + + ngOnDestroy(): void{ while(this.subscriptions.length > 0){ this.subscriptions.pop().unsubscribe() } + while (this.nehubaViewerSubscriptions.length > 0) { + this.nehubaViewerSubscriptions.pop().unsubscribe() + } } - public toggleOctantRemoval(flag: boolean){ + public toggleOctantRemoval(flag: boolean): void{ if (!this.nehubaViewerInstance) { this.log.error(`this.nehubaViewerInstance is not yet available`) return @@ -316,10 +290,13 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ this.nehubaViewerInstance.toggleOctantRemoval(flag) } - createNehubaInstance(template: any, lifeCycle: INehubaLifecycleHook = {}){ + async createNehubaInstance(nehubaConfig: NehubaConfig): Promise<void>{ this.clear() + + await new Promise(rs => setTimeout(rs, 0)) + this.iavNehubaViewerContainerViewerLoading.emit(true) - this.cr = this.el.createComponent(this.nehubaViewerFactory) + this.cr = this.el.createComponent(this.componentFactory) if (this.navService.storeNav) { this.nehubaViewerInstance.initNav = { @@ -328,46 +305,18 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ } } - const { nehubaConfig, name } = template + if (this.gpuLimit) { + const initialNgState = nehubaConfig && nehubaConfig.dataset && nehubaConfig.dataset.initialNgState + // the correct key is gpuMemoryLimit + initialNgState.gpuMemoryLimit = this.gpuLimit + } /** * apply viewer config such as gpu limit */ - const { gpuLimit = null } = this.viewerConfig this.nehubaViewerInstance.config = nehubaConfig - this.nehubaViewerInstance.lifecycle = lifeCycle - - if (gpuLimit) { - const initialNgState = nehubaConfig && nehubaConfig.dataset && nehubaConfig.dataset.initialNgState - // the correct key is gpuMemoryLimit - initialNgState.gpuMemoryLimit = gpuLimit - } - - /* TODO replace with id from KG */ - this.nehubaViewerInstance.templateId = name - this.nehubaViewerSubscriptions.push( - this.nehubaViewerInstance.errorEmitter.subscribe(e => { - console.log(e) - }), - - this.nehubaViewerInstance.layersChanged.subscribe(() => { - this.store$.dispatch( - viewerStateNehubaLayerchanged() - ) - }), - - this.nehubaViewerInstance.nehubaReady.subscribe(() => { - /** - * TODO when user selects new template, window.viewer - */ - this.store$.dispatch( - ngViewerActionNehubaReady({ - nehubaReady: true, - }) - ) - }), this.nehubaViewerInstance.mouseoverSegmentEmitter.pipe( scan(accumulatorFn, new Map()), @@ -377,28 +326,15 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ this.nehubaViewerInstance.mouseoverLandmarkEmitter.pipe( distinctUntilChanged() ).subscribe(label => { - this.store$.dispatch( - uiActionMouseoverLandmark({ - landmark: label - }) - ) + console.warn(`mouseover landmark`, label) }), this.nehubaViewerInstance.mouseoverUserlandmarkEmitter.pipe( throttleTime(160, asyncScheduler, {trailing: true}), ).subscribe(label => { - this.store$.dispatch( - viewerStateMouseOverCustomLandmarkInPerspectiveView({ - payload: { label } - }) - ) - }), - - this.nehubaViewerInstance.nehubaReady.pipe( - switchMapTo(fromEvent(this.nehubaViewerInstance.elementRef.nativeElement, 'viewportToData')), - takeOnePipe() - ).subscribe((events: CustomEvent[]) => { - [0, 1, 2].forEach(idx => this.viewportToDatas[idx] = events[idx].detail.viewportToData) + const idx = Number(label.replace('label=', '')) + // TODO + // this is exclusive for vtk layer }), combineLatest([ @@ -413,46 +349,35 @@ export class NehubaViewerContainerDirective implements OnInit, OnDestroy{ ) } - clear(){ + clear(): void{ while(this.nehubaViewerSubscriptions.length > 0) { this.nehubaViewerSubscriptions.pop().unsubscribe() } - this.store$.dispatch( - ngViewerActionNehubaReady({ - nehubaReady: false, - }) - ) - this.iavNehubaViewerContainerViewerLoading.emit(false) if(this.cr) this.cr.destroy() this.el.clear() this.cr = null } - get nehubaViewerInstance(){ + get nehubaViewerInstance(): NehubaViewerUnit{ return this.cr && this.cr.instance } - isReady() { + isReady(): boolean { return !!(this.cr?.instance?.nehubaViewer?.ngviewer) } - handleMouseoverSegments(arrOfArr: [string, any][]) { + handleMouseoverSegments(arrOfArr: [string, any][]): void { const payload = arrOfArr.map( ([ngId, {segment, segmentId}]) => { return { layer: { name: ngId, }, - segment: segment || `${ngId}#${segmentId}`, + segment: segment || serializeSegment(ngId, segmentId), segmentId } }) this.mouseOverSegments.emit(payload) - this.store$.dispatch( - uiActionMouseoverSegments({ - segments: payload - }) - ) } } diff --git a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts index 28eeda243621049da1342169ef19d70e721eb459..0fbd2562a1d4f50bc39e6157aefe322747afe475 100644 --- a/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts +++ b/src/viewerModule/nehuba/nehubaViewerInterface/nehubaViewerTouch.directive.ts @@ -1,8 +1,9 @@ -import { Directive, ElementRef, Input, OnDestroy } from "@angular/core"; +import { Directive, ElementRef, Inject, Input, OnDestroy } from "@angular/core"; import { Observable, fromEvent, merge, Subscription } from "rxjs"; import { map, filter, shareReplay, switchMap, pairwise, takeUntil, switchMapTo } from "rxjs/operators"; import { getExportNehuba } from 'src/util/fn' -import { computeDistance } from "../nehubaViewer/nehubaViewer.component"; +import { computeDistance, NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; +import { NEHUBA_INSTANCE_INJTKN, takeOnePipe } from "../util"; @Directive({ selector: '[iav-viewer-touch-interface]', @@ -11,17 +12,12 @@ import { computeDistance } from "../nehubaViewer/nehubaViewer.component"; export class NehubaViewerTouchDirective implements OnDestroy{ - @Input('iav-viewer-touch-interface-v-panels') - viewerPanels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement] - @Input('iav-viewer-touch-interface-vp-to-data') - viewportToData: [any, any, any, any] - - @Input('iav-viewer-touch-interface-ngviewer') - ngViewer: any + viewportToData: any[] = [] - @Input('iav-viewer-touch-interface-nehuba-config') - nehubaConfig: any + get ngViewer(){ + return this.nehubaUnit?.nehubaViewer?.ngviewer + } private touchMove$: Observable<any> private singleTouchStart$: Observable<TouchEvent> @@ -30,7 +26,22 @@ export class NehubaViewerTouchDirective implements OnDestroy{ public translate$: Observable<any> - private findPanelIndex = (panel: HTMLElement) => this.viewerPanels.indexOf(panel) + private nehubaUnit: NehubaViewerUnit + private htmlElementIndexMap = new WeakMap<HTMLElement, number>() + private findPanelIndex(panel: HTMLElement){ + if (!this.nehubaUnit) return null + if (!this.htmlElementIndexMap.has(panel)) { + Array.from(this.nehubaUnit?.nehubaViewer?.ngviewer?.display?.panels || []).forEach((el, idx) => { + if (el['element']) { + this.htmlElementIndexMap.set( + el['element'] as HTMLElement, + idx + ) + } + }) + } + return this.htmlElementIndexMap.get(panel) + } private _exportNehuba: any private get exportNehuba(){ @@ -41,11 +52,21 @@ export class NehubaViewerTouchDirective implements OnDestroy{ } private s: Subscription[] = [] + private nehubaSub: Subscription[] = [] constructor( private el: ElementRef, + @Inject(NEHUBA_INSTANCE_INJTKN) nehuba$: Observable<NehubaViewerUnit> ){ - + if (nehuba$) { + + this.s.push( + nehuba$.subscribe(unit => { + this.nehubaUnit = unit + this.onNewNehubaUnit(unit) + }) + ) + } /** * Touchend also needs to be listened to, as user could start * with multitouch, and end up as single touch @@ -199,7 +220,7 @@ export class NehubaViewerTouchDirective implements OnDestroy{ [ev1.touches[1].screenX, ev1.touches[1].screenY], ) const factor = d1 / d2 - const { minZoom = null, maxZoom = null } = this.nehubaConfig?.layout?.useNehubaPerspective?.restrictZoomLevel || {} + const { minZoom = null, maxZoom = null } = {} const { zoomFactor } = this.ngViewer.perspectiveNavigationState if (!!minZoom && zoomFactor.value * factor < minZoom) { return } if (!!maxZoom && zoomFactor.value * factor > maxZoom) { return } @@ -249,9 +270,27 @@ export class NehubaViewerTouchDirective implements OnDestroy{ ) } - ngOnDestroy(){ + private onNewNehubaUnit(nehubaUnit: NehubaViewerUnit) { + while (this.nehubaSub.length > 0) this.nehubaSub.pop().unsubscribe() + + if (!nehubaUnit) return + + this.nehubaSub.push( + fromEvent<CustomEvent>( + nehubaUnit.elementRef.nativeElement, + 'viewportToData' + ).pipe( + takeOnePipe() + ).subscribe((events: CustomEvent[]) => { + [0, 1, 2].forEach(idx => this.viewportToData[idx] = events[idx].detail.viewportToData) + }) + ) + } + + ngOnDestroy(): void{ while(this.s.length > 0){ this.s.pop().unsubscribe() } + while (this.nehubaSub.length > 0) this.nehubaSub.pop().unsubscribe() } } diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..f2e75e77f501517ac2f166f44a1b851042458489 --- /dev/null +++ b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.component.ts @@ -0,0 +1,152 @@ +import { ChangeDetectionStrategy, Component, Inject, Input, OnChanges, OnDestroy } from "@angular/core"; +import { Store } from "@ngrx/store"; +import { isMat4 } from "common/util" +import { Observable } from "rxjs"; +import { atlasAppearance } from "src/state"; +import { NehubaViewerUnit } from ".."; +import { NEHUBA_INSTANCE_INJTKN } from "../util"; + +type Vec4 = [number, number, number, number] +type Mat4 = [Vec4, Vec4, Vec4, Vec4] + +const _VOL_DETAIL_MAP: Record<string, { shader: string, opacity: number }> = { + "PLI Fiber Orientation Red Channel": { + shader: "void main(){ float x = toNormalized(getDataValue()); if (x < 0.1) { emitTransparent(); } else { emitRGB(vec3(1.0 * x, x * 0., 0. * x )); } }", + opacity: 1 + }, + "PLI Fiber Orientation Green Channel": { + shader: "void main(){ float x = toNormalized(getDataValue()); if (x < 0.1) { emitTransparent(); } else { emitRGB(vec3(0. * x, x * 1., 0. * x )); } }", + opacity: 0.5 + }, + "PLI Fiber Orientation Blue Channel": { + shader: "void main(){ float x = toNormalized(getDataValue()); if (x < 0.1) { emitTransparent(); } else { emitRGB(vec3(0. * x, x * 0., 1.0 * x )); } }", + opacity: 0.25 + }, + "Blockface Image": { + shader: "void main(){ float x = toNormalized(getDataValue()); if (x < 0.1) { emitTransparent(); } else { emitRGB(vec3(0.8 * x, x * 1., 0.8 * x )); } }", + opacity: 1.0 + }, + "PLI Transmittance": { + shader: "void main(){ float x = toNormalized(getDataValue()); if (x > 0.9) { emitTransparent(); } else { emitRGB(vec3(x * 1., x * 0.8, x * 0.8 )); } }", + opacity: 1.0 + }, + "T2w MRI": { + shader: "void main(){ float x = toNormalized(getDataValue()); if (x < 0.1) { emitTransparent(); } else { emitRGB(vec3(0.8 * x, 0.8 * x, x * 1. )); } }", + opacity: 1 + }, + "MRI Labels": { + shader: null, + opacity: 1 + } +} + +@Component({ + selector: 'ng-layer-ctl', + templateUrl: './ngLayerCtrl.template.html', + styleUrls: [ + './ngLayerCtrl.style.css' + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) + +export class NgLayerCtrlCmp implements OnChanges, OnDestroy{ + + private onDestroyCb: (() => void)[] = [] + private removeLayer: () => void + + public showOpacityCtrl = false + public hideNgTuneCtrl = 'lower_threshold,higher_threshold,brightness,contrast,colormap,hide-threshold-checkbox' + public defaultOpacity = 1 + + @Input('ng-layer-ctl-name') + name: string + + @Input('ng-layer-ctl-src') + source: string + + @Input('ng-layer-ctl-shader') + shader: string + + opacity: number = 1.0 + @Input('ng-layer-ctl-opacity') + set _opacity(val: number | string) { + if (typeof val === 'number') { + this.opacity = val + return + } + this.opacity = Number(val) + } + + transform: Mat4 + @Input('ng-layer-ctl-transform') + set _transform(xform: string | Mat4) { + const parsedResult = typeof xform === "string" + ? JSON.parse(xform) + : xform + if (!isMat4(xform)) { + return + } + this.transform = parsedResult as Mat4 + } + + visible: boolean = true + private viewer: NehubaViewerUnit + + constructor( + private store: Store<any>, + @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Observable<NehubaViewerUnit> + ){ + const sub = nehubaViewer$.subscribe(v => this.viewer = v) + this.onDestroyCb.push( + () => sub.unsubscribe() + ) + } + + ngOnDestroy(): void { + while(this.onDestroyCb.length > 0) this.onDestroyCb.pop()() + if (this.removeLayer) { + this.removeLayer() + this.removeLayer = null + } + } + + ngOnChanges(): void { + if (this.name in _VOL_DETAIL_MAP) { + const { shader, opacity } = _VOL_DETAIL_MAP[this.name] + this.shader = shader + this.opacity = opacity + } + + if (this.name && this.source) { + const { name } = this + if (this.removeLayer) { + this.removeLayer() + this.removeLayer = null + } + this.store.dispatch( + atlasAppearance.actions.addCustomLayer({ + customLayer: { + id: name, + shader: this.shader, + transform: this.transform, + clType: 'customlayer/nglayer', + source: `precomputed://${this.source}`, + opacity: this.opacity, + } + }) + ) + this.removeLayer = () => { + this.store.dispatch( + atlasAppearance.actions.removeCustomLayer({ + id: name + }) + ) + } + } + } + + toggleVisibility(): void{ + this.visible = !this.visible + this.viewer.nehubaViewer.ngviewer.layerManager.getLayerByName(this.name).setVisible(this.visible) + } +} diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.style.css b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.style.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html new file mode 100644 index 0000000000000000000000000000000000000000..e3442c7b4323dac373e0b9110549b8c97a206a3e --- /dev/null +++ b/src/viewerModule/nehuba/ngLayerCtl/ngLayerCtrl.template.html @@ -0,0 +1,22 @@ +<div [ngClass]="{ 'text-muted': !visible }"> + + <button mat-icon-button (click)="toggleVisibility()"> + <i [ngClass]="visible ? 'fa-eye' : 'fa-eye-slash'" class="far"></i> + </button> + + <span> + {{ name }} + </span> + + <button mat-icon-button (click)="showOpacityCtrl = !showOpacityCtrl"> + <i class="fas fa-cog"></i> + </button> + + <ng-template [ngIf]="showOpacityCtrl"> + <ng-layer-tune + [ngLayerName]="name" + [hideCtrl]="hideNgTuneCtrl" + [opacity]="defaultOpacity"> + </ng-layer-tune> + </ng-template> +</div> diff --git a/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts index 7be4aa437b59c3f92b75a3eb7607f32978d46de5..ce0afa8fa8632b433b5998eed1107654fdc4dfa2 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts +++ b/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts @@ -1,9 +1,9 @@ -import { async, ComponentFixture, TestBed } from "@angular/core/testing" +import { ComponentFixture, TestBed } from "@angular/core/testing" import { CommonModule } from "@angular/common" import { AngularMaterialModule } from "src/sharedModules" import { StatusCardComponent } from "./statusCard.component" import { Directive, Component } from "@angular/core" -import { Observable, of } from "rxjs" +import { of } from "rxjs" import { ShareModule } from "src/share" import { StateModule } from "src/state" import { MockStore, provideMockStore } from "@ngrx/store/testing" @@ -12,11 +12,10 @@ import { MatSlideToggle } from "@angular/material/slide-toggle" import { NoopAnimationsModule } from "@angular/platform-browser/animations" import { FormsModule, ReactiveFormsModule } from "@angular/forms" import { UtilModule } from "src/util" -import { viewerConfigSelectorUseMobileUi } from "src/services/state/viewerConfig.store.helper" -import { viewerStateNavigationStateSelector, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors" -import * as util from '../util' -import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions" +import * as configSvc from '../config.service' import {QuickTourModule} from "src/ui/quickTour/module"; +import { atlasSelection } from "src/state" +import { SapiSpaceModel } from "src/atlasComponents/sapi" @Directive({ selector: '[iav-auth-auth-state]', @@ -36,8 +35,8 @@ class MockSigninModal{} describe('> statusCard.component.ts', () => { describe('> StatusCardComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ + beforeEach(async () => { + await TestBed.configureTestingModule({ imports: [ CommonModule, AngularMaterialModule, @@ -64,12 +63,15 @@ describe('> statusCard.component.ts', () => { }) ] }).compileComponents() - })) + }) beforeEach(() => { const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerConfigSelectorUseMobileUi, false) + + mockStore.overrideSelector(atlasSelection.selectors.selectedTemplate, { + '@id': 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588' + } as SapiSpaceModel) }) @@ -139,24 +141,29 @@ describe('> statusCard.component.ts', () => { perspectiveOrientation: [1,0,0,0] } - const mockNavState = { - orientation: [0,0,0,1], - position: [10,20,30], - perspectiveZoom: 1e6, - zoom: 1e6, - perspectiveOrientation: [0,0,0,1] + const mockNehubaConfig = { + dataset: { + initialNgState: { + navigation: { + pose: { + orientation: [0,0,0,1], + position: [10, 20, 30] + }, + zoomFactor: 1e6 + } + } + } } - const mockTemplate = { foo:'bar', nehubaConfig: { foo2: 'bar2' } } - let getNavigationStateFromConfigSpy: jasmine.Spy = jasmine.createSpy('getNavigationStateFromConfig').and.returnValue(mockNavState) + let getNavigationStateFromConfigSpy: jasmine.Spy = jasmine.createSpy('getNavigationStateFromConfig').and.returnValue(mockNehubaConfig) beforeEach(() => { const mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectedTemplatePureSelector, mockTemplate) - mockStore.overrideSelector(viewerStateNavigationStateSelector, mockCurrNavigation) - - spyOnProperty(util, 'getNavigationStateFromConfig').and.returnValue(getNavigationStateFromConfigSpy) + mockStore.overrideSelector(atlasSelection.selectors.selectedTemplate, null) + mockStore.overrideSelector(atlasSelection.selectors.navigation, mockCurrNavigation) + spyOnProperty(configSvc, 'getNehubaConfig').and.returnValue(getNavigationStateFromConfigSpy) + fixture = TestBed.createComponent(StatusCardComponent) fixture.detectChanges() fixture.componentInstance.showFull = true @@ -180,17 +187,17 @@ describe('> statusCard.component.ts', () => { fixture.detectChanges() const overrideObj = {} - if (method === 'rotation') overrideObj['orientation'] = mockNavState['orientation'] - if (method === 'position') overrideObj['position'] = mockNavState['position'] - if (method === 'zoom') overrideObj['zoom'] = mockNavState['zoom'] + if (method === 'rotation') overrideObj['orientation'] = mockNehubaConfig.dataset.initialNgState.navigation.pose.orientation + if (method === 'position') overrideObj['position'] = mockNehubaConfig.dataset.initialNgState.navigation.pose.position + if (method === 'zoom') overrideObj['zoom'] = mockNehubaConfig.dataset.initialNgState.navigation.zoomFactor expect(idspatchSpy).toHaveBeenCalledWith( - viewerStateChangeNavigation({ + atlasSelection.actions.navigateTo({ navigation: { ...mockCurrNavigation, ...overrideObj, - positionReal: false, - animation: {}, - } + }, + physical: true, + animation: true }) ) }) diff --git a/src/viewerModule/nehuba/statusCard/statusCard.component.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.ts index 6ac175a1d93dba044ed00e3a7d5e09bc6857dd31..26d6786e8b7f7ea3723eb47d7a32bc299c46b720 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.component.ts +++ b/src/viewerModule/nehuba/statusCard/statusCard.component.ts @@ -16,11 +16,13 @@ import { MatBottomSheet } from "@angular/material/bottom-sheet"; import { MatDialog } from "@angular/material/dialog"; import { ARIA_LABELS, QUICKTOUR_DESC } from 'common/constants' import { FormControl } from "@angular/forms"; -import { viewerStateNavigationStateSelector, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors"; -import { viewerStateChangeNavigation } from "src/services/state/viewerState/actions"; -import { getNavigationStateFromConfig, NEHUBA_INSTANCE_INJTKN } from '../util' +import { NEHUBA_INSTANCE_INJTKN } from '../util' import { IQuickTourData } from "src/ui/quickTour/constrants"; +import { actions } from "src/state/atlasSelection"; +import { atlasSelection } from "src/state"; +import { SapiSpaceModel } from "src/atlasComponents/sapi"; +import { getNehubaConfig } from "../config.service"; @Component({ selector : 'iav-cmp-viewer-nehuba-status', @@ -31,7 +33,7 @@ export class StatusCardComponent implements OnInit, OnChanges{ private _nehubaViewer: NehubaViewerUnit; - get nehubaViewer(){ + get nehubaViewer(): NehubaViewerUnit{ return this._nehubaViewer } set nehubaViewer(v: NehubaViewerUnit) { @@ -43,7 +45,7 @@ export class StatusCardComponent implements OnInit, OnChanges{ public arialabel = ARIA_LABELS.STATUS_PANEL public showFull = false - private selectedTemplatePure: any + private selectedTemplate: SapiSpaceModel private currentNavigation: any private subscriptions: Subscription[] = [] @@ -57,9 +59,6 @@ export class StatusCardComponent implements OnInit, OnChanges{ } public SHARE_BTN_ARIA_LABEL = ARIA_LABELS.SHARE_BTN - public COPY_URL_TO_CLIPBOARD_ARIA_LABEL = ARIA_LABELS.SHARE_COPY_URL_CLIPBOARD - public SHARE_CUSTOM_URL_ARIA_LABEL = ARIA_LABELS.SHARE_CUSTOM_URL - public SHARE_CUSTOM_URL_DIALOG_ARIA_LABEL = ARIA_LABELS.SHARE_CUSTOM_URL_DIALOG public SHOW_FULL_STATUS_PANEL_ARIA_LABEL = ARIA_LABELS.SHOW_FULL_STATUS_PANEL public HIDE_FULL_STATUS_PANEL_ARIA_LABEL = ARIA_LABELS.HIDE_FULL_STATUS_PANEL constructor( @@ -90,18 +89,18 @@ export class StatusCardComponent implements OnInit, OnChanges{ this.subscriptions.push( this.store$.pipe( - select(viewerStateSelectedTemplatePureSelector) - ).subscribe(n => this.selectedTemplatePure = n) + select(atlasSelection.selectors.selectedTemplate) + ).subscribe(n => this.selectedTemplate = n) ) this.subscriptions.push( this.store$.pipe( - select(viewerStateNavigationStateSelector) + select(atlasSelection.selectors.navigation) ).subscribe(nav => this.currentNavigation = nav) ) } - ngOnChanges() { + ngOnChanges(): void { if (this.nehubaViewer?.viewerPosInReal$ && this.nehubaViewer?.viewerPosInVoxel$) { this.navVal$ = combineLatest([ this.statusPanelRealSpace$, @@ -151,7 +150,7 @@ export class StatusCardComponent implements OnInit, OnChanges{ startWith(true) ) - public textNavigateTo(string: string) { + public textNavigateTo(string: string): void { if (string.split(/[\s|,]+/).length >= 3 && string.split(/[\s|,]+/).slice(0, 3).every(entry => !isNaN(Number(entry.replace(/mm/, ''))))) { const pos = (string.split(/[\s|,]+/).slice(0, 3).map((entry) => Number(entry.replace(/mm/, '')) * (this.statusPanelRealSpace ? 1000000 : 1))) this.nehubaViewer.setNavigationState({ @@ -163,7 +162,7 @@ export class StatusCardComponent implements OnInit, OnChanges{ } } - showBottomSheet(tmpl: TemplateRef<any>){ + showBottomSheet(tmpl: TemplateRef<any>): void{ this.bottomSheet.open(tmpl) } @@ -176,28 +175,31 @@ export class StatusCardComponent implements OnInit, OnChanges{ * * the info re: nehubaViewer can stay there, too */ - public resetNavigation({rotation: rotationFlag = false, position: positionFlag = false, zoom : zoomFlag = false}: {rotation?: boolean, position?: boolean, zoom?: boolean}) { + public resetNavigation({rotation: rotationFlag = false, position: positionFlag = false, zoom : zoomFlag = false}: {rotation?: boolean, position?: boolean, zoom?: boolean}): void { + const config = getNehubaConfig(this.selectedTemplate) const { orientation, - position, - zoom - } = getNavigationStateFromConfig(this.selectedTemplatePure.nehubaConfig) + position + } = config.dataset.initialNgState.navigation.pose + const { + zoomFactor: zoom + } = config.dataset.initialNgState.navigation this.store$.dispatch( - viewerStateChangeNavigation({ + actions.navigateTo({ navigation: { ...this.currentNavigation, ...(rotationFlag ? { orientation: orientation } : {}), ...(positionFlag ? { position: position } : {}), ...(zoomFlag ? { zoom: zoom } : {}), - positionReal : false, - animation : {}, - } + }, + physical: true, + animation: true }) ) } - openDialog(tmpl: TemplateRef<any>, options) { + openDialog(tmpl: TemplateRef<any>, options: { ariaLabel: string }): void { const { ariaLabel } = options this.dialog.open(tmpl, { ariaLabel diff --git a/src/viewerModule/nehuba/statusCard/statusCard.template.html b/src/viewerModule/nehuba/statusCard/statusCard.template.html index d3086513b1c12eb6207f9b142a66777cb59cde88..d4daef878ea439f321f40fe5ebb06f6124e70867 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.template.html +++ b/src/viewerModule/nehuba/statusCard/statusCard.template.html @@ -3,7 +3,7 @@ [quick-tour-order]="quickTourData.order" #statusCardQT="quickTour"> <mat-card *ngIf="showFull; else showMin" - class="expandedContainer p-2 pt-1"> + class="expandedContainer sxplr-p-2 sxplr-pt-1"> <mat-card-content> @@ -53,7 +53,7 @@ <mat-slide-toggle [formControl]="statusPanelFormCtrl" - class="pl-2 pr-2"> + class="sxplr-pl-2 sxplr-pr-2"> </mat-slide-toggle> <span class="d-flex align-items center"> @@ -79,7 +79,7 @@ <div class="w-0 position-relative"> <button - (click)="showBottomSheet(shareTmpl)" + sxplr-share-view [attr.aria-label]="SHARE_BTN_ARIA_LABEL" mat-icon-button class="position-absolute share-btn"> @@ -106,12 +106,12 @@ <!-- minimised status bar --> <ng-template #showMin> - <div class="iv-custom-comp text overflow-visible text-nowrap d-inline-flex align-items-center m-1 mt-3" + <div class="sxplr-custom-cmp text of-visible text-nowrap d-inline-flex align-items-center m-1 mt-3" iav-media-query #media="iavMediaQuery"> <i aria-label="viewer navigation" class="fas fa-compass"></i> - <span *ngIf="(media.mediaBreakPoint$ | async) < 3" class="pl-2"> + <span *ngIf="(media.mediaBreakPoint$ | async) < 3" class="sxplr-pl-2"> {{ navVal$ | async }} </span> @@ -124,62 +124,3 @@ </button> </div> </ng-template> - -<!-- share template --> -<ng-template #shareTmpl> - <h4 class="mat-h4"> - Share via - </h4> - <mat-nav-list> - <mat-list-item iav-clipboard-copy - [attr.aria-label]="COPY_URL_TO_CLIPBOARD_ARIA_LABEL" - [attr.tab-index]="10"> - <mat-icon - class="mr-4" - fontSet="fas" - fontIcon="fa-copy"> - </mat-icon> - <span> - Copy link to this view - </span> - </mat-list-item> - <mat-list-item - [attr.aria-label]="SHARE_CUSTOM_URL_ARIA_LABEL" - [attr.tab-index]="10" - (click)="openDialog(shareSaneUrl, { ariaLabel: SHARE_CUSTOM_URL_ARIA_LABEL })" - [matTooltip]="SHARE_CUSTOM_URL_ARIA_LABEL" - > - <mat-icon - class="mr-4" - fontSet="fas" - fontIcon="fa-link"> - </mat-icon> - - <span> - Create custom URL - </span> - - </mat-list-item> - </mat-nav-list> -</ng-template> - -<ng-template #shareSaneUrl> - <h2 mat-dialog-title> - Create custom URL - </h2> - - <div mat-dialog-content> - <iav-sane-url iav-state-aggregator - [stateTobeSaved]="stateAggregator.jsonifiedState$ | async" - #stateAggregator="iavStateAggregator"> - </iav-sane-url> - </div> - - <div mat-dialog-actions - class="d-flex justify-content-center"> - <button mat-button - mat-dialog-close> - close - </button> - </div> -</ng-template> diff --git a/src/viewerModule/nehuba/store/actions.ts b/src/viewerModule/nehuba/store/actions.ts index 38c6572e1259651318bede3684278e71179f3daa..c8392e03b7fa423286c416c9c67fc83ad2cb4dfd 100644 --- a/src/viewerModule/nehuba/store/actions.ts +++ b/src/viewerModule/nehuba/store/actions.ts @@ -1,15 +1,8 @@ import { createAction, props } from "@ngrx/store"; -import { INgLayerInterface } from "src/services/state/ngViewerState.store"; + import { NEHUBA_VIEWER_FEATURE_KEY } from "../constants"; import { IAuxMesh } from "./type"; -export const actionAddNgLayer = createAction( - `[${NEHUBA_VIEWER_FEATURE_KEY}] [addNgLayer]`, - props<{ - layers: INgLayerInterface[] - }>() -) - export const actionSetAuxMesh = createAction( `[${NEHUBA_VIEWER_FEATURE_KEY}] [setAuxMesh]`, props<{ @@ -30,7 +23,3 @@ export const actionSetAuxMeshes = createAction( payload: IAuxMesh[] }>() ) - -export const actionClearAuxMeshes = createAction( - `[${NEHUBA_VIEWER_FEATURE_KEY}] [clearAuxMeshes]` -) diff --git a/src/viewerModule/nehuba/store/index.ts b/src/viewerModule/nehuba/store/index.ts index a384638d8bf82bd1b38296d9a061405b552cd7f1..686993e8c27a8dae11574a1355ae53c0e76696f9 100644 --- a/src/viewerModule/nehuba/store/index.ts +++ b/src/viewerModule/nehuba/store/index.ts @@ -1,8 +1,6 @@ export { - actionAddNgLayer, actionRemoveAuxMesh, actionSetAuxMesh, - actionClearAuxMeshes, actionSetAuxMeshes, } from './actions' export { @@ -14,5 +12,4 @@ export { export { IAuxMesh, INehubaFeature, - INgLayerInterface } from './type' diff --git a/src/viewerModule/nehuba/store/type.ts b/src/viewerModule/nehuba/store/type.ts index 4b1d4d3d69ab657aa67edbd18512397cda07d495..d45347a166b1630d9ecc47cebbfaa48a3a95985b 100644 --- a/src/viewerModule/nehuba/store/type.ts +++ b/src/viewerModule/nehuba/store/type.ts @@ -1,3 +1,5 @@ +import { atlasAppearance } from "src/state" + export interface IAuxMesh { ['@id']: string name: string @@ -8,19 +10,9 @@ export interface IAuxMesh { visible: boolean } -export interface INgLayerInterface { - name: string // displayName - source: string - mixability: string // base | mixable | nonmixable - annotation?: string // - id?: string // unique identifier - visible?: boolean - shader?: string - transform?: any -} export interface INehubaFeature { - layers: INgLayerInterface[] + layers: atlasAppearance.NgLayerCustomLayer[] panelMode: string panelOrder: string octantRemoval: boolean diff --git a/src/viewerModule/nehuba/store/util.ts b/src/viewerModule/nehuba/store/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..14a59dc1d78250caf449c252eacd8acd80e9524a --- /dev/null +++ b/src/viewerModule/nehuba/store/util.ts @@ -0,0 +1,10 @@ +import { SapiParcellationModel, SapiRegionModel } from "src/atlasComponents/sapi"; + +export type ParcVolumeSpec = { + volumeSrc: string + parcellation: SapiParcellationModel + regions: { + labelIndex: number + region: SapiRegionModel + }[] +} diff --git a/src/viewerModule/nehuba/touchSideClass.directive.ts b/src/viewerModule/nehuba/touchSideClass.directive.ts deleted file mode 100644 index 44cdac937990acc2b79bb03a70d545fb8030af1b..0000000000000000000000000000000000000000 --- a/src/viewerModule/nehuba/touchSideClass.directive.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Directive, ElementRef, Input, OnDestroy, OnInit } from "@angular/core"; -import { select, Store } from "@ngrx/store"; -import { Observable, Subscription } from "rxjs"; -import { distinctUntilChanged, tap } from "rxjs/operators"; -import { ngViewerSelectorPanelMode } from "src/services/state/ngViewerState/selectors"; -import { IavRootStoreInterface } from "src/services/stateStore.service"; -import { addTouchSideClasses, removeTouchSideClasses } from "src/viewerModule/nehuba/util"; - - -@Directive({ - selector: '[touch-side-class]', - exportAs: 'touchSideClass', -}) - -export class TouchSideClass implements OnDestroy, OnInit { - @Input('touch-side-class') - public panelNativeIndex: number - - public panelMode: string - private panelMode$: Observable<string> - - private subscriptions: Subscription[] = [] - - constructor( - private store$: Store<IavRootStoreInterface>, - private el: ElementRef, - ) { - - this.panelMode$ = this.store$.pipe( - select(ngViewerSelectorPanelMode), - distinctUntilChanged(), - tap(mode => this.panelMode = mode), - ) - } - - public ngOnInit() { - this.subscriptions.push( - - this.panelMode$.subscribe(panelMode => { - removeTouchSideClasses(this.el.nativeElement) - addTouchSideClasses(this.el.nativeElement, this.panelNativeIndex, panelMode) - }), - ) - } - - public ngOnDestroy() { - while (this.subscriptions.length > 0) { - this.subscriptions.pop().unsubscribe() - } - } -} diff --git a/src/viewerModule/nehuba/types.ts b/src/viewerModule/nehuba/types.ts index 35cca295acae535d68ba6bb98ce282650c830e45..6fd0dddab5fce72039cd81a92ac3b377c294f8fe 100644 --- a/src/viewerModule/nehuba/types.ts +++ b/src/viewerModule/nehuba/types.ts @@ -1,3 +1,4 @@ +import { SapiRegionModel } from "src/atlasComponents/sapi"; import { INavObj } from "./navigation.service"; export type TNehubaContextInfo = { @@ -9,5 +10,6 @@ export type TNehubaContextInfo = { nehuba: { layerName: string labelIndices: number[] + regions: SapiRegionModel[] }[] } diff --git a/src/viewerModule/nehuba/util.spec.ts b/src/viewerModule/nehuba/util.spec.ts index 0b022b7281e319318a0ae261ef8827b77334e7c1..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/src/viewerModule/nehuba/util.spec.ts +++ b/src/viewerModule/nehuba/util.spec.ts @@ -1,141 +0,0 @@ -import { cvtNavigationObjToNehubaConfig } from './util' - -const currentNavigation = { - position: [4, 5, 6], - orientation: [0, 0, 0, 1], - perspectiveOrientation: [ 0, 0, 0, 1], - perspectiveZoom: 2e5, - zoom: 1e5 -} - -const defaultPerspectiveZoom = 1e6 -const defaultZoom = 1e6 - -const defaultNavigationObject = { - orientation: [0, 0, 0, 1], - perspectiveOrientation: [0 , 0, 0, 1], - perspectiveZoom: defaultPerspectiveZoom, - zoom: defaultZoom, - position: [0, 0, 0], - positionReal: true -} - -const defaultNehubaConfigObject = { - perspectiveOrientation: [0, 0, 0, 1], - perspectiveZoom: 1e6, - navigation: { - pose: { - position: { - voxelCoordinates: [0, 0, 0], - voxelSize: [1,1,1] - }, - orientation: [0, 0, 0, 1], - }, - zoomFactor: defaultZoom - } -} - -const bigbrainNehubaConfig = { - "showDefaultAnnotations": false, - "layers": { - }, - "navigation": { - "pose": { - "position": { - "voxelSize": [ - 21166.666015625, - 20000, - 21166.666015625 - ], - "voxelCoordinates": [ - -21.8844051361084, - 16.288618087768555, - 28.418994903564453 - ] - } - }, - "zoomFactor": 350000 - }, - "perspectiveOrientation": [ - 0.3140767216682434, - -0.7418519854545593, - 0.4988985061645508, - -0.3195493221282959 - ], - "perspectiveZoom": 1922235.5293810747 -} - -describe('> util.ts', () => { - - describe('> cvtNavigationObjToNehubaConfig', () => { - const validNavigationObj = currentNavigation - describe('> if inputs are malformed', () => { - describe('> if navigation object is malformed, uses navigation default object', () => { - it('> if navigation object is null', () => { - const v1 = cvtNavigationObjToNehubaConfig(null, bigbrainNehubaConfig) - const v2 = cvtNavigationObjToNehubaConfig(defaultNavigationObject, bigbrainNehubaConfig) - expect(v1).toEqual(v2) - }) - it('> if navigation object is undefined', () => { - const v1 = cvtNavigationObjToNehubaConfig(undefined, bigbrainNehubaConfig) - const v2 = cvtNavigationObjToNehubaConfig(defaultNavigationObject, bigbrainNehubaConfig) - expect(v1).toEqual(v2) - }) - - it('> if navigation object is otherwise malformed', () => { - const v1 = cvtNavigationObjToNehubaConfig({foo: 'bar'}, bigbrainNehubaConfig) - const v2 = cvtNavigationObjToNehubaConfig(defaultNavigationObject, bigbrainNehubaConfig) - expect(v1).toEqual(v2) - - const v3 = cvtNavigationObjToNehubaConfig({}, bigbrainNehubaConfig) - const v4 = cvtNavigationObjToNehubaConfig(defaultNavigationObject, bigbrainNehubaConfig) - expect(v3).toEqual(v4) - }) - }) - - describe('> if nehubaConfig object is malformed, use default nehubaConfig obj', () => { - it('> if nehubaConfig is null', () => { - const v1 = cvtNavigationObjToNehubaConfig(validNavigationObj, null) - const v2 = cvtNavigationObjToNehubaConfig(validNavigationObj, defaultNehubaConfigObject) - expect(v1).toEqual(v2) - }) - - it('> if nehubaConfig is undefined', () => { - const v1 = cvtNavigationObjToNehubaConfig(validNavigationObj, undefined) - const v2 = cvtNavigationObjToNehubaConfig(validNavigationObj, defaultNehubaConfigObject) - expect(v1).toEqual(v2) - }) - - it('> if nehubaConfig is otherwise malformed', () => { - const v1 = cvtNavigationObjToNehubaConfig(validNavigationObj, {}) - const v2 = cvtNavigationObjToNehubaConfig(validNavigationObj, defaultNehubaConfigObject) - expect(v1).toEqual(v2) - - const v3 = cvtNavigationObjToNehubaConfig(validNavigationObj, {foo: 'bar'}) - const v4 = cvtNavigationObjToNehubaConfig(validNavigationObj, defaultNehubaConfigObject) - expect(v3).toEqual(v4) - }) - }) - }) - it('> converts navigation object and reference nehuba config object to navigation object', () => { - const convertedVal = cvtNavigationObjToNehubaConfig(validNavigationObj, bigbrainNehubaConfig) - const { perspectiveOrientation, orientation, zoom, perspectiveZoom, position } = validNavigationObj - - expect(convertedVal).toEqual({ - navigation: { - pose: { - position: { - voxelSize: bigbrainNehubaConfig.navigation.pose.position.voxelSize, - voxelCoordinates: [0, 1, 2].map(idx => position[idx] / bigbrainNehubaConfig.navigation.pose.position.voxelSize[idx]) - }, - orientation - }, - zoomFactor: zoom - }, - perspectiveOrientation: perspectiveOrientation, - perspectiveZoom: perspectiveZoom - }) - }) - }) - -}) diff --git a/src/viewerModule/nehuba/util.ts b/src/viewerModule/nehuba/util.ts index 398ad2d82aafbbdafc6c8b8c50d72239a91ded92..71d7a985a09c1ecfa3dd79fdac4c75f3c380bef9 100644 --- a/src/viewerModule/nehuba/util.ts +++ b/src/viewerModule/nehuba/util.ts @@ -1,9 +1,9 @@ import { InjectionToken } from '@angular/core' import { Observable, pipe } from 'rxjs' import { filter, scan, take } from 'rxjs/operators' -import { PANELS } from 'src/services/state/ngViewerState.store.helper' -import { getViewer } from 'src/util/fn' +import { getExportNehuba, getViewer } from 'src/util/fn' import { NehubaViewerUnit } from './nehubaViewer/nehubaViewer.component' +import { userInterface } from 'src/state' const flexContCmnCls = ['w-100', 'h-100', 'd-flex', 'justify-content-center', 'align-items-stretch'] @@ -37,49 +37,48 @@ const left = true const right = true const bottom = true -const mapModeIdxClass = new Map() +const mapModeIdxClass = new Map< + userInterface.PanelMode, Map<number,{ + top?: boolean + bottom?: boolean + left?: boolean + right?: boolean + }> +>() -mapModeIdxClass.set(PANELS.FOUR_PANEL, new Map([ +mapModeIdxClass.set("FOUR_PANEL", new Map([ [0, { top, left }], [1, { top, right }], [2, { bottom, left }], [3, { right, bottom }], ])) -mapModeIdxClass.set(PANELS.SINGLE_PANEL, new Map([ +mapModeIdxClass.set("SINGLE_PANEL", new Map([ [0, { top, left, right, bottom }], [1, {}], [2, {}], [3, {}], ])) -mapModeIdxClass.set(PANELS.H_ONE_THREE, new Map([ +mapModeIdxClass.set("H_ONE_THREE", new Map([ [0, { top, left, bottom }], [1, { top, right }], [2, { right }], [3, { bottom, right }], ])) -mapModeIdxClass.set(PANELS.V_ONE_THREE, new Map([ +mapModeIdxClass.set("V_ONE_THREE", new Map([ [0, { top, left, right }], [1, { bottom, left }], [2, { bottom }], [3, { bottom, right }], ])) -export const removeTouchSideClasses = (panel: HTMLElement) => { - panel.classList.remove( - `touch-top`, - `touch-left`, - `touch-right`, - `touch-bottom`) - return panel -} - +type PanelTouchSide = 'left' | 'right' | 'top' | 'bottom' /** * gives a clue of the approximate location of the panel, allowing position of checkboxes/scale bar to be placed in unobtrustive places */ -export const panelTouchSide = (panel: HTMLElement, { top: touchTop, left: touchLeft, right: touchRight, bottom: touchBottom }: any) => { +export const panelTouchSide = (panel: HTMLElement, { top: touchTop, left: touchLeft, right: touchRight, bottom: touchBottom }: Partial<Record<PanelTouchSide, boolean>>): HTMLElement => { if (touchTop) { panel.classList.add(`touch-top`) } if (touchLeft) { panel.classList.add(`touch-left`) } if (touchRight) { panel.classList.add(`touch-right`) } @@ -87,7 +86,7 @@ export const panelTouchSide = (panel: HTMLElement, { top: touchTop, left: touchL return panel } -export const addTouchSideClasses = (panel: HTMLElement, actualOrderIndex: number, panelMode: string) => { +export const addTouchSideClasses = (panel: HTMLElement, actualOrderIndex: number, panelMode: userInterface.PanelMode): HTMLElement => { if (actualOrderIndex < 0) { return panel } @@ -100,10 +99,10 @@ export const addTouchSideClasses = (panel: HTMLElement, actualOrderIndex: number return panelTouchSide(panel, classArg) } -export const getHorizontalOneThree = (panels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { +export const getHorizontalOneThree = (panels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement]): HTMLElement => { washPanels(panels) - panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, PANELS.H_ONE_THREE)) + panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, "H_ONE_THREE")) const majorContainer = makeCol(panels[0]) const minorContainer = makeCol(panels[1], panels[2], panels[3]) @@ -114,10 +113,10 @@ export const getHorizontalOneThree = (panels: [HTMLElement, HTMLElement, HTMLEle return makeRow(majorContainer, minorContainer) } -export const getVerticalOneThree = (panels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { +export const getVerticalOneThree = (panels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement]): HTMLDivElement => { washPanels(panels) - panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, PANELS.V_ONE_THREE)) + panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, "V_ONE_THREE")) const majorContainer = makeRow(panels[0]) const minorContainer = makeRow(panels[1], panels[2], panels[3]) @@ -128,10 +127,10 @@ export const getVerticalOneThree = (panels: [HTMLElement, HTMLElement, HTMLEleme return makeCol(majorContainer, minorContainer) } -export const getFourPanel = (panels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { +export const getFourPanel = (panels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement]): HTMLDivElement => { washPanels(panels) - panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, PANELS.FOUR_PANEL)) + panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, "FOUR_PANEL")) const majorContainer = makeRow(panels[0], panels[1]) const minorContainer = makeRow(panels[2], panels[3]) @@ -142,10 +141,10 @@ export const getFourPanel = (panels: [HTMLElement, HTMLElement, HTMLElement, HTM return makeCol(majorContainer, minorContainer) } -export const getSinglePanel = (panels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => { +export const getSinglePanel = (panels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement]): HTMLDivElement => { washPanels(panels) - panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, PANELS.SINGLE_PANEL)) + panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, "SINGLE_PANEL")) const majorContainer = makeRow(panels[0]) const minorContainer = makeRow(panels[1], panels[2], panels[3]) @@ -158,60 +157,15 @@ export const getSinglePanel = (panels: [HTMLElement, HTMLElement, HTMLElement, H return makeRow(majorContainer, minorContainer) } -export const isIdentityQuat = ori => Math.abs(ori[0]) < 1e-6 +export const isIdentityQuat = (ori: number[]): boolean => Math.abs(ori[0]) < 1e-6 && Math.abs(ori[1]) < 1e-6 && Math.abs(ori[2]) < 1e-6 && Math.abs(ori[3] - 1) < 1e-6 -export const getNavigationStateFromConfig = nehubaConfig => { - const { - navigation = {}, - perspectiveOrientation = [0, 0, 0, 1], - perspectiveZoom = 1e7 - } = (nehubaConfig && nehubaConfig.dataset && nehubaConfig.dataset.initialNgState) || {} - - const { - zoomFactor = 3e5, - pose = {} - } = navigation || {} - - const { - voxelSize = [1e6, 1e6, 1e6], - voxelCoordinates = [0, 0, 0] - } = (pose && pose.position) || {} - - const { - orientation = [0, 0, 0, 1] - } = pose || {} - - return { - orientation, - perspectiveOrientation, - perspectiveZoom, - position: [0, 1, 2].map(idx => voxelSize[idx] * voxelCoordinates[idx]), - zoom: zoomFactor - } -} - -export const calculateSliceZoomFactor = (originalZoom) => originalZoom - ? 700 * originalZoom / Math.min(window.innerHeight, window.innerWidth) - : 1e7 - -export const singleLmUnchanged = (lm: {id: string, position: [number, number, number]}, map: Map<string, [number, number, number]>) => - map.has(lm.id) && map.get(lm.id).every((value, idx) => value === lm.position[idx]) - -export const userLmUnchanged = (oldlms, newlms) => { - const oldmap = new Map(oldlms.map(lm => [lm.id, lm.position])) - const newmap = new Map(newlms.map(lm => [lm.id, lm.position])) - - return oldlms.every(lm => singleLmUnchanged(lm, newmap as Map<string, [number, number, number]>)) - && newlms.every(lm => singleLmUnchanged(lm, oldmap as Map<string, [number, number, number]>)) -} - -export const importNehubaFactory = appendSrc => { - let pr: Promise<any> +export const importNehubaFactory = (appendSrc: (src: string) => Promise<void>): () => Promise<void> => { + let pr: Promise<void> return () => { - if ((window as any).export_nehuba) return Promise.resolve() + if (getExportNehuba()) return Promise.resolve() if (pr) return pr pr = appendSrc('main.bundle.js') @@ -220,42 +174,6 @@ export const importNehubaFactory = appendSrc => { } } - -export const isFirstRow = (cell: HTMLElement) => { - const { parentElement: row } = cell - const { parentElement: container } = row - return container.firstElementChild === row -} - -export const isFirstCell = (cell: HTMLElement) => { - const { parentElement: row } = cell - return row.firstElementChild === cell -} - -export const scanSliceViewRenderFn: (acc: [boolean, boolean, boolean], curr: CustomEvent) => [boolean, boolean, boolean] = (acc, curr) => { - - const target = curr.target as HTMLElement - const targetIsFirstRow = isFirstRow(target) - const targetIsFirstCell = isFirstCell(target) - const idx = targetIsFirstRow - ? targetIsFirstCell - ? 0 - : 1 - : targetIsFirstCell - ? 2 - : null - - const returnAcc = [...acc] - const num1 = typeof curr.detail.missingChunks === 'number' ? curr.detail.missingChunks : 0 - const num2 = typeof curr.detail.missingImageChunks === 'number' ? curr.detail.missingImageChunks : 0 - if (num1 < 0 && num2 < 0) { - returnAcc[idx] = true - } else { - returnAcc[idx] = Math.max(num1, num2) > 0 - } - return returnAcc as [boolean, boolean, boolean] -} - export const takeOnePipe = () => { return pipe( @@ -290,40 +208,20 @@ export const takeOnePipe = () => { export const NEHUBA_INSTANCE_INJTKN = new InjectionToken<Observable<NehubaViewerUnit>>('NEHUBA_INSTANCE_INJTKN') -export function cvtNavigationObjToNehubaConfig(navigationObj, nehubaConfigObj){ - const { - orientation = [0, 0, 0, 1], - perspectiveOrientation = [0, 0, 0, 1], - perspectiveZoom = 1e6, - zoom = 1e6, - position = [0, 0, 0], - positionReal = true, - } = navigationObj || {} - - const voxelSize = (() => { - const { - navigation = {} - } = nehubaConfigObj || {} - const { pose = {} } = navigation - const { position = {} } = pose - const { voxelSize = [1, 1, 1] } = position - return voxelSize - })() +export function serializeSegment(ngId: string, label: number | string): string{ + return `${ngId}#${label}` +} +export function deserializeSegment(id: string): {ngId: string, label: number} { + const split = id.split('#') + if (split.length !== 2) { + throw new Error(`deserializeSegment error at ${id}. expecting splitting # to result in length 2, got ${split.length}`) + } + if (isNaN(Number(split[1]))) { + throw new Error(`deserializeSegment error at ${id}. expecting second element to be numberable. It was not.`) + } return { - perspectiveOrientation, - perspectiveZoom, - navigation: { - pose: { - position: { - voxelCoordinates: positionReal - ? [0, 1, 2].map(idx => position[idx] / voxelSize[idx]) - : position, - voxelSize - }, - orientation, - }, - zoomFactor: zoom - } + ngId: split[0], + label: Number(split[1]) } } diff --git a/src/viewerModule/nehuba/viewerCtrl/change-perspective-orientation/changePerspectiveOrientation.component.html b/src/viewerModule/nehuba/viewerCtrl/change-perspective-orientation/changePerspectiveOrientation.component.html index c194ea9659faa473dbf30de56ace6b5e6e83901d..4aa94fa5d1bce357ba7caba1c6a1b3f901cdc30c 100644 --- a/src/viewerModule/nehuba/viewerCtrl/change-perspective-orientation/changePerspectiveOrientation.component.html +++ b/src/viewerModule/nehuba/viewerCtrl/change-perspective-orientation/changePerspectiveOrientation.component.html @@ -4,7 +4,7 @@ <mat-menu #perspectiveOrientationMenu="matMenu"> - <div class="d-flex align-items-center iv-custom-comp text"> + <div class="d-flex align-items-center sxplr-custom-cmp text"> <button mat-button color="basic" class="flex-grow-1 text-left font-weight-normal" (click)="set3DViewPoint('coronal', 'first')"> Coronal view @@ -15,7 +15,7 @@ </button> </div> - <div class="d-flex align-items-center iv-custom-comp text"> <!--mat-menu-item--> + <div class="d-flex align-items-center sxplr-custom-cmp text"> <!--mat-menu-item--> <button mat-button color="basic" class="flex-grow-1 text-left font-weight-normal" (click)="set3DViewPoint('sagittal', 'first')"> Sagittal view @@ -26,7 +26,7 @@ </button> </div> - <div class="d-flex align-items-center iv-custom-comp text"> <!--mat-menu-item--> + <div class="d-flex align-items-center sxplr-custom-cmp text"> <!--mat-menu-item--> <button mat-button color="basic" class="flex-grow-1 text-left font-weight-normal" (click)="set3DViewPoint('axial', 'first')"> Axial view diff --git a/src/viewerModule/nehuba/viewerCtrl/change-perspective-orientation/changePerspectiveOrientation.component.ts b/src/viewerModule/nehuba/viewerCtrl/change-perspective-orientation/changePerspectiveOrientation.component.ts index 0c4bdb1300fb0e7bc094a81d2dcac2d39f67c0d1..9f9d10f7b6467a186111d749e22b17dbebeb3129 100644 --- a/src/viewerModule/nehuba/viewerCtrl/change-perspective-orientation/changePerspectiveOrientation.component.ts +++ b/src/viewerModule/nehuba/viewerCtrl/change-perspective-orientation/changePerspectiveOrientation.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import {viewerStateChangeNavigation} from "src/services/state/viewerState/actions"; import {Store} from "@ngrx/store"; +import { actions } from 'src/state/atlasSelection'; @Component({ selector: 'app-change-perspective-orientation', @@ -22,10 +22,11 @@ export class ChangePerspectiveOrientationComponent { const orientation = this.viewOrientations[plane][view === 'first'? 0 : 1] this.store$.dispatch( - viewerStateChangeNavigation({ + actions.navigateTo({ navigation: { perspectiveOrientation: orientation, - } + }, + animation: true }) ) } diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts index 54b6e08fa3d70dea75042b7372251e8be51e2de7..c5042ecfc22e3134ab6af2c967bdad86bf5d4933 100644 --- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts +++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts @@ -4,16 +4,16 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms" import { MockStore, provideMockStore } from "@ngrx/store/testing" import { BehaviorSubject, of } from "rxjs" import { ComponentsModule } from "src/components" -import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState.store.helper" -import { viewerStateCustomLandmarkSelector, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors" import { AngularMaterialModule } from "src/sharedModules" -import {PureContantService, UtilModule} from "src/util" +import { UtilModule } from "src/util" import { actionSetAuxMeshes, selectorAuxMeshes } from "../../store" import { NEHUBA_INSTANCE_INJTKN } from "../../util" import { ViewerCtrlCmp } from "./viewerCtrlCmp.component" import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed' import { HarnessLoader } from "@angular/cdk/testing" import { MatSlideToggleHarness } from '@angular/material/slide-toggle/testing' +import { atlasAppearance, atlasSelection } from "src/state" + describe('> viewerCtrlCmp.component.ts', () => { describe('> ViewerCtrlCmp', () => { @@ -65,14 +65,6 @@ describe('> viewerCtrlCmp.component.ts', () => { useFactory: () => { return new BehaviorSubject(mockNehubaViewer).asObservable() } - }, - { - provide: PureContantService, - useFactory: () => { - return { - getViewerConfig: jasmine.createSpy('getViewerConfig') - } - } } ] }).compileComponents() @@ -80,9 +72,8 @@ describe('> viewerCtrlCmp.component.ts', () => { }) beforeEach(() => { mockStore = TestBed.inject(MockStore) - mockStore.overrideSelector(viewerStateSelectedTemplatePureSelector, {}) - mockStore.overrideSelector(ngViewerSelectorOctantRemoval, true) - mockStore.overrideSelector(viewerStateCustomLandmarkSelector, []) + mockStore.overrideSelector(atlasSelection.selectors.selectedTemplate, {} as any) + mockStore.overrideSelector(atlasAppearance.selectors.octantRemoval, true) mockStore.overrideSelector(selectorAuxMeshes, []) }) @@ -140,39 +131,6 @@ describe('> viewerCtrlCmp.component.ts', () => { ).toHaveBeenCalledWith(!wasChecked) }) }) - - describe('> toggle delineation', () => { - - let toggleDelination: jasmine.Spy - const toggleName = 'toggle-delineation' - beforeEach(() => { - toggleDelination = spyOn<any>(fixture.componentInstance, 'toggleParcVsbl') - }) - afterEach(() => { - toggleDelination.calls.reset() - }) - - it('> toggleslider should exist', async () => { - const slideToggle = await loader.getAllHarnesses( - MatSlideToggleHarness.with({ - name: toggleName, - }) - ) - expect(slideToggle.length).toBe(1) - }) - - it('> toggling it should result in setOctantRemoval to be called', async () => { - const slideToggle = await loader.getAllHarnesses( - MatSlideToggleHarness.with({ - name: toggleName, - }) - ) - await slideToggle[0].toggle() - expect( - toggleDelination - ).toHaveBeenCalled() - }) - }) }) describe('> UI aux meshes', () => { @@ -236,120 +194,5 @@ describe('> viewerCtrlCmp.component.ts', () => { ) }) }) - - describe('> flagDelin', () => { - let toggleParcVsblSpy: jasmine.Spy - beforeEach(() => { - fixture = TestBed.createComponent(ViewerCtrlCmp) - toggleParcVsblSpy = spyOn(fixture.componentInstance as any, 'toggleParcVsbl') - fixture.detectChanges() - }) - it('> calls toggleParcVsbl', () => { - toggleParcVsblSpy.and.callFake(() => {}) - fixture.componentInstance.flagDelin = false - expect(toggleParcVsblSpy).toHaveBeenCalled() - }) - }) - describe('> toggleParcVsbl', () => { - let getViewerConfigSpy: jasmine.Spy - let getLayerByNameSpy: jasmine.Spy - beforeEach(() => { - const pureCstSvc = TestBed.inject(PureContantService) - getLayerByNameSpy = mockNehubaViewer.nehubaViewer.ngviewer.layerManager.getLayerByName - getViewerConfigSpy = pureCstSvc.getViewerConfig as jasmine.Spy - fixture = TestBed.createComponent(ViewerCtrlCmp) - fixture.detectChanges() - }) - - it('> calls pureSvc.getViewerConfig', async () => { - getViewerConfigSpy.and.returnValue({}) - await fixture.componentInstance['toggleParcVsbl']() - expect(getViewerConfigSpy).toHaveBeenCalled() - }) - - describe('> if _flagDelin is true', () => { - beforeEach(() => { - fixture.componentInstance['_flagDelin'] = true - fixture.componentInstance['hiddenLayerNames'] = [ - 'foo', - 'bar', - 'baz' - ] - }) - it('> go through all hideen layer names and set them to true', async () => { - const setVisibleSpy = jasmine.createSpy('setVisible') - getLayerByNameSpy.and.returnValue({ - setVisible: setVisibleSpy - }) - await fixture.componentInstance['toggleParcVsbl']() - expect(getLayerByNameSpy).toHaveBeenCalledTimes(3) - for (const arg of ['foo', 'bar', 'baz']) { - expect(getLayerByNameSpy).toHaveBeenCalledWith(arg) - } - expect(setVisibleSpy).toHaveBeenCalledTimes(3) - expect(setVisibleSpy).toHaveBeenCalledWith(true) - expect(setVisibleSpy).not.toHaveBeenCalledWith(false) - }) - it('> hiddenLayerNames resets', async () => { - await fixture.componentInstance['toggleParcVsbl']() - expect(fixture.componentInstance['hiddenLayerNames']).toEqual([]) - }) - }) - - describe('> if _flagDelin is false', () => { - let managedLayerSpyProp: jasmine.Spy - let setVisibleSpy: jasmine.Spy - beforeEach(() => { - fixture.componentInstance['_flagDelin'] = false - setVisibleSpy = jasmine.createSpy('setVisible') - getLayerByNameSpy.and.returnValue({ - setVisible: setVisibleSpy - }) - getViewerConfigSpy.and.resolveTo({ - 'foo': {}, - 'bar': {}, - 'baz': {} - }) - managedLayerSpyProp = spyOnProperty(mockNehubaViewer.nehubaViewer.ngviewer.layerManager, 'managedLayers') - managedLayerSpyProp.and.returnValue([{ - visible: true, - name: 'foo' - }, { - visible: false, - name: 'bar' - }, { - visible: true, - name: 'baz' - }]) - }) - - afterEach(() => { - managedLayerSpyProp.calls.reset() - }) - - it('> calls schedulRedraw', async () => { - await fixture.componentInstance['toggleParcVsbl']() - await new Promise(rs => requestAnimationFrame(rs)) - expect(mockNehubaViewer.nehubaViewer.ngviewer.display.scheduleRedraw).toHaveBeenCalled() - }) - - it('> only calls setVisible false on visible layers', async () => { - await fixture.componentInstance['toggleParcVsbl']() - expect(getLayerByNameSpy).toHaveBeenCalledTimes(2) - - for (const arg of ['foo', 'baz']) { - expect(getLayerByNameSpy).toHaveBeenCalledWith(arg) - } - expect(setVisibleSpy).toHaveBeenCalledTimes(2) - expect(setVisibleSpy).toHaveBeenCalledWith(false) - expect(setVisibleSpy).not.toHaveBeenCalledWith(true) - }) - - it('> sets hiddenLayerNames correctly', async () => { - await fixture.componentInstance['toggleParcVsbl']() - expect(fixture.componentInstance['hiddenLayerNames']).toEqual(['foo', 'baz']) - }) - }) - }) }) }) diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts index d46a84cfab7d129e94d0c542fd718fa7390e0b6c..2a44980c9fd4639d227129dc84005ce306323d64 100644 --- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts +++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.ts @@ -1,16 +1,13 @@ -import { Component, HostBinding, Inject, Optional } from "@angular/core"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnInit, Optional } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { combineLatest, merge, Observable, of, Subscription } from "rxjs"; -import {filter, map, pairwise, withLatestFrom} from "rxjs/operators"; -import { ngViewerActionSetPerspOctantRemoval } from "src/services/state/ngViewerState/actions"; -import { ngViewerSelectorOctantRemoval } from "src/services/state/ngViewerState/selectors"; -import { viewerStateCustomLandmarkSelector, viewerStateGetSelectedAtlas, viewerStateSelectedTemplatePureSelector } from "src/services/state/viewerState/selectors"; +import { merge, Observable, of, Subscription } from "rxjs"; +import { pairwise, withLatestFrom} from "rxjs/operators"; import { NehubaViewerUnit } from "src/viewerModule/nehuba"; import { NEHUBA_INSTANCE_INJTKN } from "src/viewerModule/nehuba/util"; import { ARIA_LABELS } from 'common/constants' import { actionSetAuxMeshes, selectorAuxMeshes } from "../../store"; import { FormBuilder, FormControl, FormGroup } from "@angular/forms"; -import {PureContantService} from "src/util"; +import { atlasAppearance } from "src/state"; @Component({ selector: 'viewer-ctrl-component', @@ -18,105 +15,40 @@ import {PureContantService} from "src/util"; styleUrls: [ './viewerCtrlCmp.style.css' ], - exportAs: 'viewerCtrlCmp' + exportAs: 'viewerCtrlCmp', + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ViewerCtrlCmp{ +export class ViewerCtrlCmp implements OnInit{ public ARIA_LABELS = ARIA_LABELS - @HostBinding('attr.darktheme') - darktheme = false - - private selectedAtlasId: string - private selectedTemplateId: string - - private _flagDelin = true - get flagDelin(){ - return this._flagDelin - } - set flagDelin(flag){ - this._flagDelin = flag - this.toggleParcVsbl() - } - private sub: Subscription[] = [] - private hiddenLayerNames: string[] = [] - private _removeOctantFlag: boolean - get removeOctantFlag(){ + private _removeOctantFlag: boolean = true + get removeOctantFlag(): boolean{ return this._removeOctantFlag } - set removeOctantFlag(val){ + set removeOctantFlag(val: boolean){ if (val === this._removeOctantFlag) return this._removeOctantFlag = val this.setOctantRemoval(this._removeOctantFlag) + this.cdr.detectChanges() } public nehubaViewerPerspectiveOctantRemoval$ = this.store$.pipe( - select(ngViewerSelectorOctantRemoval), + select(atlasAppearance.selectors.octantRemoval), ) - public customLandmarks$: Observable<any> = this.store$.pipe( - select(viewerStateCustomLandmarkSelector), - map(lms => lms.map(lm => ({ - ...lm, - geometry: { - position: lm.position - } - }))), - ) - - public auxMeshFormGroup: FormGroup + public auxMeshFormGroup: FormGroup = this.formBuilder.group({}) private auxMeshesNamesSet: Set<string> = new Set() public auxMeshes$ = this.store$.pipe( select(selectorAuxMeshes), ) - private nehubaInst: NehubaViewerUnit - - get ngViewer() { - return this.nehubaInst?.nehubaViewer.ngviewer || (window as any).viewer - } - - constructor( - private store$: Store<any>, - formBuilder: FormBuilder, - private pureConstantService: PureContantService, - @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) private nehubaInst$: Observable<NehubaViewerUnit>, - ){ - - this.auxMeshFormGroup = formBuilder.group({}) - - - if (this.nehubaInst$) { - this.sub.push( - combineLatest([ - this.customLandmarks$, - this.nehubaInst$, - ]).pipe( - filter(([_, nehubaInst]) => !!nehubaInst), - ).subscribe(([landmarks, nehubainst]) => { - this.setOctantRemoval(landmarks.length === 0) - nehubainst.updateUserLandmarks(landmarks) - }), - this.nehubaInst$.subscribe(nehubaInst => this.nehubaInst = nehubaInst) - ) - } else { - console.warn(`NEHUBA_INSTANCE_INJTKN not provided`) - } + ngOnInit(): void { this.sub.push( - this.store$.select(viewerStateGetSelectedAtlas) - .pipe(filter(a => !!a)) - .subscribe(sa => this.selectedAtlasId = sa['@id']), - this.store$.pipe( - select(viewerStateSelectedTemplatePureSelector) - ).subscribe(tmpl => { - this.selectedTemplateId = tmpl['@id'] - const { useTheme } = tmpl || {} - this.darktheme = useTheme === 'dark' - }), this.nehubaViewerPerspectiveOctantRemoval$.subscribe( flag => this.removeOctantFlag = flag @@ -141,6 +73,7 @@ export class ViewerCtrlCmp{ this.auxMeshesNamesSet.add(mesh.ngId) this.auxMeshFormGroup.addControl(mesh['@id'], new FormControl(mesh.visible)) } + this.cdr.detectChanges() }), this.auxMeshFormGroup.valueChanges.pipe( @@ -165,48 +98,29 @@ export class ViewerCtrlCmp{ }) ) } + this.cdr.detectChanges() }) ) } - private async toggleParcVsbl(){ - const viewerConfig = await this.pureConstantService.getViewerConfig(this.selectedAtlasId, this.selectedTemplateId, null) - - if (this.flagDelin) { - for (const name of this.hiddenLayerNames) { - const l = this.ngViewer.layerManager.getLayerByName(name) - l && l.setVisible(true) - } - this.hiddenLayerNames = [] - } else { - this.hiddenLayerNames = [] - const segLayerNames: string[] = [] - for (const layer of this.ngViewer.layerManager.managedLayers) { - if (layer.visible && layer.name in viewerConfig) { - segLayerNames.push(layer.name) - } - } - for (const name of segLayerNames) { - const l = this.ngViewer.layerManager.getLayerByName(name) - l && l.setVisible(false) - this.hiddenLayerNames.push( name ) - } - } - - requestAnimationFrame(() => { - this.ngViewer.display.scheduleRedraw() - }) + constructor( + private store$: Store<any>, + private formBuilder: FormBuilder, + private cdr: ChangeDetectorRef, + @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) private nehubaInst$: Observable<NehubaViewerUnit>, + ){ + } - public setOctantRemoval(octantRemovalFlag: boolean) { + public setOctantRemoval(octantRemovalFlag: boolean): void { this.store$.dispatch( - ngViewerActionSetPerspOctantRemoval({ - octantRemovalFlag + atlasAppearance.actions.setOctantRemoval({ + flag: octantRemovalFlag }) ) } - public trackByAtId(_idx: number, obj: { ['@id']: string }) { + public trackByAtId(_idx: number, obj: { ['@id']: string }): string { return obj['@id'] } } diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html index 76aa348a8f659039b6c0ece363db4a79921be756..50f326abfe12df716ca25a14c9b73043cb53b004 100644 --- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html +++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.template.html @@ -1,28 +1,11 @@ -<h3 class="iv-custom-comp text mat-h3"> - Volumes -</h3> - -<mat-slide-toggle [(ngModel)]="flagDelin" - #delinToggle="matSlideToggle" - [iav-key-listener]="[{ type: 'keydown', key: 'q', target: 'document', capture: true }]" - (iav-key-event)="delinToggle.toggle()" - name="toggle-delineation"> - - <markdown-dom class="d-inline-block iv-custom-comp text" - markdown="Show delineations `[q]`"> - </markdown-dom> -</mat-slide-toggle> - -<mat-divider class="mt-2 mb-2"></mat-divider> - -<h3 class="iv-custom-comp text mat-h3"> +<h3 class="sxplr-custom-cmp text mat-h3"> Perspective View </h3> <mat-slide-toggle [(ngModel)]="removeOctantFlag" [aria-label]="ARIA_LABELS.TOGGLE_FRONTAL_OCTANT" name="remove-frontal-octant"> - <span class="iv-custom-comp text"> + <span class="sxplr-custom-cmp text"> Remove frontal octant </span> </mat-slide-toggle> @@ -34,11 +17,12 @@ [formControlName]="auxMesh['@id']" class="d-block" [name]="'toggle-aux-mesh-' + auxMesh['@id']"> - <span class="iv-custom-comp text"> + <span class="sxplr-custom-cmp text"> {{ auxMesh.displayName || auxMesh.name }} </span> </mat-slide-toggle> </form> </ng-container> -<app-change-perspective-orientation></app-change-perspective-orientation> +<!-- TODO menu no longer showing, likely something to do with detaching cdr on nehubaGlue.component --> +<app-change-perspective-orientation *ngIf="false"></app-change-perspective-orientation> diff --git a/src/viewerModule/pipes/nehubaVCtxToBbox.pipe.ts b/src/viewerModule/pipes/nehubaVCtxToBbox.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..e3ceaad4673e198e11eba7ca257ed0a334bb1ac6 --- /dev/null +++ b/src/viewerModule/pipes/nehubaVCtxToBbox.pipe.ts @@ -0,0 +1,37 @@ +import { TContextArg } from './../viewer.interface'; +import { Pipe, PipeTransform } from "@angular/core"; + +type Point = [number, number, number] +type BBox = [Point, Point] + +const MAGIC_RADIUS = 256 + +@Pipe({ + name: "nehubaVCtxToBbox", + pure: true +}) + +export class NehubaVCtxToBbox implements PipeTransform{ + public transform(event: TContextArg<'nehuba' | 'threeSurfer'>, unit: string = "mm"): BBox{ + if (!event) { + return null + } + if (event.viewerType === 'threeSurfer') { + return null + } + let divisor = 1 + if (unit === "mm") { + divisor = 1e6 + } + const { payload } = event as TContextArg<'nehuba'> + + if (!payload.nav) return null + + const { position, zoom } = payload.nav + // position is in nm + // zoom can be directly applied as a multiple + const min = position.map(v => (v - (MAGIC_RADIUS * zoom)) / divisor) as Point + const max = position.map(v => (v + (MAGIC_RADIUS * zoom)) / divisor) as Point + return [min, max] + } +} diff --git a/src/viewerModule/threeSurfer/module.ts b/src/viewerModule/threeSurfer/module.ts index 21f11580befb07553f6f613b3b0f86fa2366e8d6..7a1f1c3e3c908f64451cebc7db7e513d7a1f9e40 100644 --- a/src/viewerModule/threeSurfer/module.ts +++ b/src/viewerModule/threeSurfer/module.ts @@ -1,11 +1,14 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; +import { StoreModule } from "@ngrx/store"; import { ComponentsModule } from "src/components"; import { AngularMaterialModule } from "src/sharedModules"; import { UtilModule } from "src/util"; import { ThreeSurferGlueCmp } from "./threeSurferGlue/threeSurfer.component"; import { ThreeSurferViewerConfig } from "./tsViewerConfig/tsViewerConfig.component"; +import { nameSpace, reducer, ThreeSurferEffects } from "./store" +import { EffectsModule } from "@ngrx/effects"; @NgModule({ imports: [ @@ -14,6 +17,13 @@ import { ThreeSurferViewerConfig } from "./tsViewerConfig/tsViewerConfig.compone UtilModule, FormsModule, ComponentsModule, + StoreModule.forFeature( + nameSpace, + reducer + ), + EffectsModule.forFeature([ + ThreeSurferEffects, + ]) ], declarations: [ ThreeSurferGlueCmp, diff --git a/src/viewerModule/threeSurfer/store/actions.ts b/src/viewerModule/threeSurfer/store/actions.ts new file mode 100644 index 0000000000000000000000000000000000000000..613fcbbfc7b943dfc412717987314a2b34fb5fb6 --- /dev/null +++ b/src/viewerModule/threeSurfer/store/actions.ts @@ -0,0 +1,9 @@ +import { createAction, props } from "@ngrx/store"; +import { nameSpace } from "./const" + +export const selectVolumeById = createAction( + `${nameSpace} selectVolumeById`, + props<{ + id: string + }>() +) diff --git a/src/viewerModule/threeSurfer/store/const.ts b/src/viewerModule/threeSurfer/store/const.ts new file mode 100644 index 0000000000000000000000000000000000000000..5643484e1b0628d986eb70dec222903a1fbfe6cd --- /dev/null +++ b/src/viewerModule/threeSurfer/store/const.ts @@ -0,0 +1 @@ +export const nameSpace = `[threeSurfer]` \ No newline at end of file diff --git a/src/viewerModule/threeSurfer/store/effects.ts b/src/viewerModule/threeSurfer/store/effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..4fa4c9f190783d18fac844ec69019352bfb6c855 --- /dev/null +++ b/src/viewerModule/threeSurfer/store/effects.ts @@ -0,0 +1,156 @@ +import { Injectable } from "@angular/core"; +import { createEffect } from "@ngrx/effects"; +import { select, Store } from "@ngrx/store"; +import { EMPTY, forkJoin, merge, Observable, of, pipe, throwError } from "rxjs"; +import { debounceTime, map, switchMap, withLatestFrom, filter, shareReplay, distinctUntilChanged } from "rxjs/operators"; +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiSpaceModel } from "src/atlasComponents/sapi"; +import { atlasAppearance, atlasSelection } from "src/state"; +import { ThreeSurferCustomLabelLayer, ThreeSurferCustomLayer } from "src/state/atlasAppearance/const"; +import * as selectors from "./selectors" +import * as actions from "./actions" + +export const fromATP = { + getThreeSurfaces: (sapi: SAPI) => { + return pipe( + filter( + ({ atlas, template, parcellation }: {atlas: SapiAtlasModel, template: SapiSpaceModel, parcellation: SapiParcellationModel}) => !!atlas && !!template && !!parcellation + ), + switchMap(({ atlas, template, parcellation }: {atlas: SapiAtlasModel, template: SapiSpaceModel, parcellation: SapiParcellationModel}) => + forkJoin({ + surfaces: sapi.getSpace(atlas["@id"], template["@id"]).getVolumes().pipe( + map( + volumes => volumes.filter(vol => vol.data.type === "gii") + ) + ), + labels: sapi.getParcellation(atlas["@id"], parcellation["@id"]).getVolumes().pipe( + map( + volumes => volumes.filter(vol => + vol.data.type === "gii-label" && + vol.data.space["@id"] === template["@id"] + ) + ) + ) + }) + ) + ) + } +} + +@Injectable() +export class ThreeSurferEffects { + + private onATP$ = this.store.pipe( + atlasSelection.fromRootStore.distinctATP() + ) + + private selectedSurfaceId$ = this.store.pipe( + select(selectors.getSelectedVolumeId), + distinctUntilChanged() + ) + + private threeSurferBaseCustomLayers$: Observable<ThreeSurferCustomLayer[]> = this.store.pipe( + select(atlasAppearance.selectors.customLayers), + map( + cl => cl.filter(layer => layer.clType === "baselayer/threesurfer") as ThreeSurferCustomLayer[] + ) + ) + + onATPClearBaseLayers = createEffect(() => merge( + this.onATP$, + this.selectedSurfaceId$, + ).pipe( + withLatestFrom( + this.threeSurferBaseCustomLayers$ + ), + switchMap(([_, layers]) => + of( + ...layers.map(layer => + atlasAppearance.actions.removeCustomLayer({ + id: layer.id + }) + ) + ) + ) + )) + + public onATPDebounceThreeSurferLayers$ = this.onATP$.pipe( + debounceTime(16), + fromATP.getThreeSurfaces(this.sapi), + shareReplay(1), + ) + + onATPDebounceHasSurfaceVolumes = createEffect(() => this.onATPDebounceThreeSurferLayers$.pipe( + switchMap(({ surfaces }) => { + const defaultSurface = surfaces.find(s => s.metadata.shortName === "pial") || surfaces[0] + if (!defaultSurface) return EMPTY + return of( + actions.selectVolumeById({ + id: defaultSurface["@id"] + }) + ) + }) + )) + + onSurfaceSelected = createEffect(() => this.selectedSurfaceId$.pipe( + switchMap(id => this.onATPDebounceThreeSurferLayers$.pipe( + switchMap(({ surfaces }) => { + if (surfaces.length === 0) return EMPTY + + const layers: ThreeSurferCustomLayer[] = [] + /** + * select the pial or first one by default + */ + const selectedSrc = surfaces.find(s => s["@id"] === id) + + if (!(selectedSrc.data?.url_map)) { + return throwError(`Expecting surfaces[0].data.url_map to be defined, but is not.`) + } + + for (const key in selectedSrc.data.url_map) { + layers.push({ + clType: 'baselayer/threesurfer', + id: `${selectedSrc["@id"]}-${key}`, + name: `${selectedSrc["@id"]}-${key}`, + laterality: key as 'left' | 'right', + source: selectedSrc.data.url_map[key] + }) + } + return of(...[ + ...layers.map(customLayer => + atlasAppearance.actions.addCustomLayer({ + customLayer + }) + ) + ]) + }) + )) + )) + + onATPDebounceAddBaseLayers$ = createEffect(() => this.onATPDebounceThreeSurferLayers$.pipe( + switchMap(({ labels }) => { + const labelMaps: ThreeSurferCustomLabelLayer[] = [] + for (const label of labels) { + labelMaps.push({ + clType: 'baselayer/threesurfer-label', + id: `${label["@id"]}-${label.metadata.shortName}`, + laterality: label.metadata.shortName as 'left' | 'right', + source: label.data.url + }) + } + return of( + ...labelMaps.map(customLayer => + atlasAppearance.actions.addCustomLayer({ + customLayer + }) + ) + ) + }) + )) + + constructor( + private store: Store, + private sapi: SAPI, + ){ + + } +} \ No newline at end of file diff --git a/src/viewerModule/threeSurfer/store/index.ts b/src/viewerModule/threeSurfer/store/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e84f687f54017d80038471ae103fa2f4131e3626 --- /dev/null +++ b/src/viewerModule/threeSurfer/store/index.ts @@ -0,0 +1,5 @@ +export { reducer, Store, defaultStore } from "./store" +export * as actions from "./actions" +export * as selectors from "./selectors" +export { nameSpace } from "./const" +export { ThreeSurferEffects } from "./effects" \ No newline at end of file diff --git a/src/viewerModule/threeSurfer/store/selectors.ts b/src/viewerModule/threeSurfer/store/selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..82c32604a57a26efac7881f9345fdb6c38cf9c2c --- /dev/null +++ b/src/viewerModule/threeSurfer/store/selectors.ts @@ -0,0 +1,10 @@ +import { createSelector } from "@ngrx/store" +import { nameSpace } from "./const" +import { Store } from "./store" + +const selectStore = state => state[nameSpace] as Store + +export const getSelectedVolumeId = createSelector( + selectStore, + ({ selectedVolumeId }) => selectedVolumeId +) diff --git a/src/viewerModule/threeSurfer/store/store.ts b/src/viewerModule/threeSurfer/store/store.ts new file mode 100644 index 0000000000000000000000000000000000000000..b830d4278e88bfea95ca9a5f0697fb68f7db2171 --- /dev/null +++ b/src/viewerModule/threeSurfer/store/store.ts @@ -0,0 +1,24 @@ +import { createReducer, on } from "@ngrx/store"; +import * as actions from "./actions" + +export type Store = { + selectedVolumeId: string +} + + +export const defaultStore: Store = { + selectedVolumeId: null +} + +export const reducer = createReducer( + defaultStore, + on( + actions.selectVolumeById, + (state, { id }) => { + return { + ...state, + selectedVolumeId: id + } + } + ) +) diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts index a00b376604e92ed7e4328b4289c227813f475836..f04ad6cc1b0a5b85875c6bc247150b737eb5605c 100644 --- a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts +++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts @@ -1,21 +1,21 @@ -import { Component, Input, Output, EventEmitter, ElementRef, OnChanges, OnDestroy, AfterViewInit, Inject, Optional } from "@angular/core"; +import { Component, Output, EventEmitter, ElementRef, OnDestroy, AfterViewInit, Inject, Optional, ChangeDetectionStrategy } from "@angular/core"; import { EnumViewerEvt, IViewer, TViewerEvent } from "src/viewerModule/viewer.interface"; -import { TThreeSurferConfig, TThreeSurferMode } from "../types"; -import { parseContext } from "../util"; -import { retry, flattenRegions } from 'common/util' -import { Observable, Subject } from "rxjs"; -import { debounceTime, filter, switchMap } from "rxjs/operators"; +import { combineLatest, Observable, Subject } from "rxjs"; +import { debounceTime, distinctUntilChanged, filter, map, shareReplay } from "rxjs/operators"; import { ComponentStore } from "src/viewerModule/componentStore"; import { select, Store } from "@ngrx/store"; -import { viewerStateChangeNavigation, viewerStateSetSelectedRegions } from "src/services/state/viewerState/actions"; -import { viewerStateSelectorNavigation } from "src/services/state/viewerState/selectors"; import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util"; -import { REGION_OF_INTEREST } from "src/util/interfaces"; import { MatSnackBar } from "@angular/material/snack-bar"; import { CONST } from 'common/constants' -import { API_SERVICE_SET_VIEWER_HANDLE_TOKEN, TSetViewerHandle } from "src/atlasViewer/atlasViewer.apiService.service"; -import { getUuid, switchMapWaitFor } from "src/util/fn"; +import { getUuid } from "src/util/fn"; import { AUTO_ROTATE, TInteralStatePayload, ViewerInternalStateSvc } from "src/viewerModule/viewerInternalState.service"; +import { atlasAppearance, atlasSelection } from "src/state"; +import { ThreeSurferCustomLabelLayer, ThreeSurferCustomLayer, ColorMapCustomLayer } from "src/state/atlasAppearance/const"; +import { SapiRegionModel, SapiVolumeModel } from "src/atlasComponents/sapi"; +import { getRegionLabelIndex } from "src/viewerModule/nehuba/config.service"; +import { arrayEqual } from "src/util/array"; +import { ThreeSurferEffects } from "../store/effects"; +import { selectors, actions } from "../store" const viewerType = 'ThreeSurfer' type TInternalState = { @@ -28,10 +28,10 @@ type TInternalState = { hemisphere: 'left' | 'right' | 'both' } const pZoomFactor = 5e3 -const preferredFsMode = 'pial' type THandlingCustomEv = { - regions: ({ name?: string, error?: string })[] + regions: SapiRegionModel[] + error?: string evMesh?: { faceIndex: number verticesIndicies: number[] @@ -39,20 +39,31 @@ type THandlingCustomEv = { } type TCameraOrientation = { - perspectiveOrientation: [number, number, number, number] + perspectiveOrientation: number[] perspectiveZoom: number } -const threshold = 1e-3 - -function getHemisphereKey(region: { name: string }){ - return /left/.test(region.name) - ? 'left' - : /right/.test(region.name) - ? 'right' - : null +type TThreeGeometry = { + visible: boolean +} +type GiiInstance = unknown +type TThreeSurfer = { + loadMesh: (url: string) => Promise<TThreeGeometry> + unloadMesh: (geom: TThreeGeometry) => void + redraw: (geom: TThreeGeometry) => void + applyColorMap: (geom: TThreeGeometry, idxMap?: number[], custom?: { usePreset?: any, custom?: Map<number, number[]> }) => void + loadColormap: (url: string) => Promise<GiiInstance> + setupAnimation: () => void + dispose: () => void + control: any + camera: any + customColormap: WeakMap<TThreeGeometry, any> } +type LateralityRecord<T> = Record<string, T> + +const threshold = 1e-3 + function cameraNavsAreSimilar(c1: TCameraOrientation, c2: TCameraOrientation){ if (c1 === c2) return true if (!!c1 && !!c2) return true @@ -75,46 +86,120 @@ function cameraNavsAreSimilar(c1: TCameraOrientation, c2: TCameraOrientation){ styleUrls: [ './threeSurfer.style.css' ], - providers: [ ComponentStore ] + providers: [ ComponentStore ], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, AfterViewInit, OnDestroy { +export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit, OnDestroy { - private loanedColorMap = new WeakSet() - - @Input() - selectedTemplate: any - - @Input() - selectedParcellation: any @Output() viewerEvent = new EventEmitter<TViewerEvent<'threeSurfer'>>() private domEl: HTMLElement - private config: TThreeSurferConfig - public modes: TThreeSurferMode[] = [] - public selectedMode: string - private mainStoreCameraNav: TCameraOrientation = null private localCameraNav: TCameraOrientation = null - public allKeys: {name: string, checked: boolean}[] = [] + public lateralityMeshRecord: LateralityRecord<{ + visible: boolean + meshLayer: ThreeSurferCustomLayer + mesh: TThreeGeometry + }> = {} + + public latLblIdxRecord: LateralityRecord<{ + indexLayer: ThreeSurferCustomLabelLayer + labelIndices: number[] + }> = {} private internalStateNext: (arg: TInteralStatePayload<TInternalState>) => void - private regionMap: Map<string, Map<number, any>> = new Map() - private mouseoverRegions = [] + private mouseoverRegions: SapiRegionModel[] = [] - private raf: number + private selectedRegions$ = this.store$.pipe( + select(atlasSelection.selectors.selectedRegions) + ) + + private customLayers$ = this.store$.pipe( + select(atlasAppearance.selectors.customLayers), + distinctUntilChanged(arrayEqual((o, n) => o.id === n.id)), + shareReplay(1) + ) + public meshLayers$: Observable<ThreeSurferCustomLayer[]> = this.customLayers$.pipe( + map(layers => layers.filter(l => l.clType === "baselayer/threesurfer") as ThreeSurferCustomLayer[]), + distinctUntilChanged(arrayEqual((o, n) => o.id === n.id)), + ) + + private vertexIndexLayers$: Observable<ThreeSurferCustomLabelLayer[]> = this.customLayers$.pipe( + map(layers => layers.filter(l => l.clType === "baselayer/threesurfer-label") as ThreeSurferCustomLabelLayer[]), + distinctUntilChanged(arrayEqual((o, n) => o.id === n.id)), + ) + + /** + * maps laterality to label index to sapi region + */ + private latLblIdxToRegionRecord: LateralityRecord<Record<number, SapiRegionModel>> = {} + private latLblIdxToRegionRecord$: Observable<LateralityRecord<Record<number, SapiRegionModel>>> = combineLatest([ + this.store$.pipe( + atlasSelection.fromRootStore.distinctATP() + ), + this.store$.pipe( + select(atlasSelection.selectors.selectedParcAllRegions), + ) + ]).pipe( + map(([ { atlas, parcellation, template }, regions]) => { + const returnObj = { + 'left': {} as Record<number, SapiRegionModel>, + 'right': {} as Record<number, SapiRegionModel> + } + + for (const region of regions) { + const idx = getRegionLabelIndex(atlas, template, parcellation, region) + if (idx) { + let key : 'left' | 'right' + if ( /left/i.test(region.name) ) key = 'left' + if ( /right/i.test(region.name) ) key = 'right' + if (!key) { + /** + * TODO + * there are ... more regions than expected, which has label index without laterality + */ + continue + } + returnObj[key][idx] = region + } + } + return returnObj + }) + ) + + /** + * colormap in use (both base & custom) + */ + + private colormapInUse: ColorMapCustomLayer + private colormaps$: Observable<ColorMapCustomLayer[]> = this.customLayers$.pipe( + map(layers => layers.filter(l => l.clType === "baselayer/colormap" || l.clType === "customlayer/colormap") as ColorMapCustomLayer[]), + ) + + /** + * show delination map + */ + private showDelineation: boolean = true + + public threeSurferSurfaceLayers$ = this.effect.onATPDebounceThreeSurferLayers$.pipe( + map(({ surfaces }) => surfaces) + ) + public selectedSurfaceLayerId$ = this.store$.pipe( + select(selectors.getSelectedVolumeId) + ) + constructor( + private effect: ThreeSurferEffects, private el: ElementRef, - private store$: Store<any>, + private store$: Store, private navStateStoreRelay: ComponentStore<{ perspectiveOrientation: [number, number, number, number], perspectiveZoom: number }>, private snackbar: MatSnackBar, @Optional() intViewerStateSvc: ViewerInternalStateSvc, - @Optional() @Inject(REGION_OF_INTEREST) private roi$: Observable<any>, @Optional() @Inject(CLICK_INTERCEPTOR_INJECTOR) clickInterceptor: ClickInterceptor, - @Optional() @Inject(API_SERVICE_SET_VIEWER_HANDLE_TOKEN) setViewerHandle: TSetViewerHandle, ){ if (intViewerStateSvc) { const { @@ -144,108 +229,6 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af this.onDestroyCb.push(() => done()) } - // set viewer handle - // the API won't be 100% compatible with ngviewer - if (setViewerHandle) { - const nyi = () => { - throw new Error(`Not yet implemented`) - } - setViewerHandle({ - add3DLandmarks: nyi, - loadLayer: nyi, - applyLayersColourMap: (map: Map<string, Map<number, { red: number, green: number, blue: number }>>) => { - if (this.loanedColorMap.has(map)) { - this.externalHemisphLblColorMap = null - } else { - - const applyCm = new Map() - for (const [hem, m] of map.entries()) { - const nMap = new Map() - applyCm.set(hem, nMap) - for (const [lbl, vals] of m.entries()) { - const { red, green, blue } = vals - nMap.set(lbl, [red/255, green/255, blue/255]) - } - } - this.externalHemisphLblColorMap = applyCm - } - this.applyColorMap() - }, - getLayersSegmentColourMap: () => { - const map = this.getColormapCopy() - const outmap = new Map<string, Map<number, { red: number, green: number, blue: number }>>() - for (const [ hem, m ] of map.entries()) { - const nMap = new Map<number, {red: number, green: number, blue: number}>() - outmap.set(hem, nMap) - for (const [ lbl, vals ] of m.entries()) { - nMap.set(lbl, { - red: vals[0] * 255, - green: vals[1] * 255, - blue: vals[2] * 255, - }) - } - } - this.loanedColorMap.add(outmap) - return outmap - }, - getNgHash: nyi, - hideAllSegments: nyi, - hideSegment: nyi, - mouseEvent: null, - mouseOverNehuba: null, - mouseOverNehubaLayers: null, - mouseOverNehubaUI: null, - moveToNavigationLoc: null, - moveToNavigationOri: null, - remove3DLandmarks: null, - removeLayer: null, - setLayerVisibility: null, - setNavigationLoc: null, - setNavigationOri: null, - showAllSegments: nyi, - showSegment: nyi, - }) - } - - this.onDestroyCb.push( - () => setViewerHandle(null) - ) - - if (this.roi$) { - const sub = this.roi$.pipe( - switchMap(switchMapWaitFor({ - condition: () => this.colormapLoaded - })) - ).subscribe(r => { - try { - if (!r) throw new Error(`No region selected.`) - const cmap = this.getColormapCopy() - const hemisphere = getHemisphereKey(r) - if (!hemisphere) { - this.snackbar.open(CONST.CANNOT_DECIPHER_HEMISPHERE, 'Dismiss', { - duration: 3000 - }) - throw new Error(CONST.CANNOT_DECIPHER_HEMISPHERE) - } - for (const [ hem, m ] of cmap.entries()) { - for (const lbl of m.keys()) { - if (hem !== hemisphere || lbl !== r.labelIndex) { - m.set(lbl, [1, 1, 1]) - } - } - } - this.internalHemisphLblColorMap = cmap - } catch (e) { - this.internalHemisphLblColorMap = null - } - - this.applyColorMap() - }) - this.onDestroyCb.push( - () => sub.unsubscribe() - ) - } - /** * intercept click and act */ @@ -264,10 +247,10 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af }) return true } + + const regions = this.mouseoverRegions.slice(0, 1) as any[] this.store$.dispatch( - viewerStateSetSelectedRegions({ - selectRegions: this.mouseoverRegions - }) + atlasSelection.actions.setSelectedRegions({ regions }) ) return true } @@ -294,11 +277,21 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af const t = new THREE.Vector3() const s = new THREE.Vector3() + /** + * ThreeJS interpretes the scene differently to neuroglancer in subtle ways. + * At [0, 0, 0, 1] decomposed camera quaternion, for example, + * - ThreeJS: view from superior -> inferior, anterior as top, right hemisphere as right + * - NG: view from from inferior -> superior, posterior as top, left hemisphere as right + * + * multiplying the exchange factor [-1, 0, 0, 0] converts ThreeJS convention to NG convention + */ const cameraM = this.tsRef.camera.matrix cameraM.decompose(t, q, s) + const exchangeFactor = new THREE.Quaternion(-1, 0, 0, 0) + try { this.navStateStoreRelay.setState({ - perspectiveOrientation: q.toArray(), + perspectiveOrientation: q.multiply(exchangeFactor).toArray(), perspectiveZoom: t.length() }) } catch (_e) { @@ -315,11 +308,11 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af */ const navStateSub = this.navStateStoreRelay.select(s => s).subscribe(v => { this.store$.dispatch( - viewerStateChangeNavigation({ + atlasSelection.actions.setNavigation({ navigation: { position: [0, 0, 0], orientation: [0, 0, 0, 1], - zoom: 1, + zoom: 1e6, perspectiveOrientation: v.perspectiveOrientation, perspectiveZoom: v.perspectiveZoom * pZoomFactor } @@ -335,7 +328,8 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af * subscribe to main store and negotiate with relay to set camera */ const navSub = this.store$.pipe( - select(viewerStateSelectorNavigation) + select(atlasSelection.selectors.navigation), + filter(v => !!v), ).subscribe(nav => { const { perspectiveOrientation, perspectiveZoom } = nav this.mainStoreCameraNav = { @@ -349,6 +343,18 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af const cameraQuat = new THREE.Quaternion(...this.mainStoreCameraNav.perspectiveOrientation) const cameraPos = new THREE.Vector3(0, 0, this.mainStoreCameraNav.perspectiveZoom / pZoomFactor) + + /** + * ThreeJS interpretes the scene differently to neuroglancer in subtle ways. + * At [0, 0, 0, 1] decomposed camera quaternion, for example, + * - ThreeJS: view from superior -> inferior, anterior as top, right hemisphere as right + * - NG: view from from inferior -> superior, posterior as top, left hemisphere as right + * + * multiplying the exchange factor [-1, 0, 0, 0] converts ThreeJS convention to NG convention + */ + const exchangeFactor = new THREE.Quaternion(-1, 0, 0, 0) + cameraQuat.multiply(exchangeFactor) + cameraPos.applyQuaternion(cameraQuat) this.toTsRef(tsRef => { tsRef.camera.position.copy(cameraPos) @@ -362,23 +368,9 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af ) } - tsRef: any - loadedMeshes: { - threeSurfer: any - mesh: string - colormap: string - hemisphere: string - vIdxArr: number[] - }[] = [] - private hemisphLblColorMap: Map<string, Map<number, [number, number, number]>> = new Map() - private internalHemisphLblColorMap: Map<string, Map<number, [number, number, number]>> - private externalHemisphLblColorMap: Map<string, Map<number, [number, number, number]>> - - get activeColorMap() { - if (this.externalHemisphLblColorMap) return this.externalHemisphLblColorMap - if (this.internalHemisphLblColorMap) return this.internalHemisphLblColorMap - return this.hemisphLblColorMap - } + private tsRef: TThreeSurfer + private selectedRegions: SapiRegionModel[] = [] + private relayStoreLock: () => void = null private tsRefInitCb: ((tsRef: any) => void)[] = [] private toTsRef(callback: (tsRef: any) => void) { @@ -389,84 +381,47 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af this.tsRefInitCb.push(callback) } - private unloadAllMeshes() { - this.allKeys = [] - while(this.loadedMeshes.length > 0) { - const m = this.loadedMeshes.pop() - this.tsRef.unloadMesh(m.threeSurfer) - } - this.hemisphLblColorMap.clear() - this.colormapLoaded = false - } + private async loadMeshes(layers: ThreeSurferCustomLayer[]) { + if (!this.tsRef) throw new Error(`loadMeshes error: this.tsRef is not defined!!`) - public async loadMode(mode: TThreeSurferMode) { - - this.unloadAllMeshes() - - this.selectedMode = mode.name - const { meshes } = mode - await retry(async () => { - for (const singleMesh of meshes) { - const { hemisphere } = singleMesh - if (!this.regionMap.has(hemisphere)) throw new Error(`regionmap does not have hemisphere defined!`) - } - }, { - timeout: 32, - retries: 10 - }) - for (const singleMesh of meshes) { - const { mesh, colormap, hemisphere } = singleMesh - this.allKeys.push({name: hemisphere, checked: true}) - - const tsM = await this.tsRef.loadMesh( - parseContext(mesh, [this.config['@context']]) - ) - - if (!this.regionMap.has(hemisphere)) continue - const rMap = this.regionMap.get(hemisphere) - const applyCM = new Map() - for (const [ lblIdx, region ] of rMap.entries()) { - applyCM.set(lblIdx, (region.rgb || [200, 200, 200]).map(v => v/255)) + /** + * remove the layers... + */ + for (const layer of layers) { + if (!!this.lateralityMeshRecord[layer.laterality]) { + this.tsRef.unloadMesh(this.lateralityMeshRecord[layer.laterality].mesh) } + } - const tsC = await this.tsRef.loadColormap( - parseContext(colormap, [this.config['@context']]) - ) - - let colorIdx = tsC[0].getData() - if (tsC[0].attributes.DataType === 'NIFTI_TYPE_INT16') { - colorIdx = (window as any).ThreeSurfer.GiftiBase.castF32UInt16(colorIdx) + for (const layer of layers) { + const threeMesh = await this.tsRef.loadMesh(layer.source) + this.lateralityMeshRecord[layer.laterality] = { + visible: true, + meshLayer: layer, + mesh: threeMesh } - - this.loadedMeshes.push({ - threeSurfer: tsM, - colormap, - mesh, - hemisphere, - vIdxArr: colorIdx - }) - - this.hemisphLblColorMap.set(hemisphere, applyCM) } - this.colormapLoaded = true - this.applyColorMap() + this.applyColor() } - private colormapLoaded = false + private async loadVertexIndexMap(layers: ThreeSurferCustomLabelLayer[]) { + if (!this.tsRef) throw new Error(`loadVertexIndexMap error: this.tsRef is not defined!!`) + for (const layer of layers) { + const giiInstance = await this.tsRef.loadColormap(layer.source) - private getColormapCopy(): Map<string, Map<number, [number, number, number]>> { - const outmap = new Map() - for (const [key, value] of this.hemisphLblColorMap.entries()) { - outmap.set(key, new Map(value)) + let labelIndices: number[] = giiInstance[0].getData() + if (giiInstance[0].attributes.DataType === 'NIFTI_TYPE_INT16') { + labelIndices = (window as any).ThreeSurfer.GiftiBase.castF32UInt16(labelIndices) + } + this.latLblIdxRecord[layer.laterality] = { + indexLayer: layer, + labelIndices + } } - return outmap + this.applyColor() } - /** - * TODO perhaps debounce calls to applycolormap - * so that the colormap doesn't "flick" - */ - private applyColorMap(){ + private applyColor() { /** * on apply color map, reset mesh visibility * this issue is more difficult to solve than first anticiplated. @@ -476,72 +431,36 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af * 2/ hide hemisphere, select region, unhide hemisphere * 3/ select region, hide hemisphere, deselect region */ - for (const key of this.allKeys) { - key.checked = true - } - for (const mesh of this.loadedMeshes) { - const { hemisphere, threeSurfer, vIdxArr } = mesh - const applyCM = this.activeColorMap.get(hemisphere) - this.tsRef.applyColorMap(threeSurfer, vIdxArr, - { - custom: applyCM - } - ) - } - } + if (!this.colormapInUse) return + if (!this.tsRef) return + + const isBaseCM = this.colormapInUse?.clType === "baselayer/colormap" - async ngOnChanges(){ - if (this.tsRef) { - this.ngOnDestroy() - this.ngAfterViewInit() - } - if (this.selectedTemplate) { + for (const laterality in this.lateralityMeshRecord) { + const { mesh } = this.lateralityMeshRecord[laterality] + if (!this.latLblIdxRecord[laterality]) continue + const { labelIndices } = this.latLblIdxRecord[laterality] - /** - * wait until threesurfer is defined in window - */ - await retry(async () => { - if (typeof (window as any).ThreeSurfer === 'undefined') throw new Error('ThreeSurfer not yet defined') - }, { - timeout: 160, - retries: 10, - }) - - this.config = this.selectedTemplate['three-surfer'] - // somehow curv ... cannot be parsed properly by gifti parser... something about points missing - this.modes = this.config.modes.filter(m => !/curv/.test(m.name)) - if (!this.tsRef) { - this.tsRef = new (window as any).ThreeSurfer(this.domEl, {highlightHovered: true}) - this.onDestroyCb.push( - () => { - this.tsRef.dispose() - this.tsRef = null - } - ) - this.tsRef.control.enablePan = false - while (this.tsRefInitCb.length > 0) this.tsRefInitCb.pop()(this.tsRef) + const lblIdxToRegionRecord = this.latLblIdxToRegionRecord[laterality] + if (!lblIdxToRegionRecord) { + this.tsRef.applyColorMap(mesh, labelIndices) + continue } - - const flattenedRegions = flattenRegions(this.selectedParcellation.regions) - for (const region of flattenedRegions) { - if (region.labelIndex) { - const hemisphere = getHemisphereKey(region) - if (!hemisphere) throw new Error(`region ${region.name} does not have hemisphere defined`) - if (!this.regionMap.has(hemisphere)) { - this.regionMap.set(hemisphere, new Map()) - } - const rMap = this.regionMap.get(hemisphere) - rMap.set(region.labelIndex, region) + const map = new Map<number, number[]>() + for (const lblIdx in lblIdxToRegionRecord) { + const region = lblIdxToRegionRecord[lblIdx] + let color: number[] + if (!this.showDelineation) { + color = [1,1,1] + } else if (isBaseCM && this.selectedRegions.length > 0 && !this.selectedRegions.includes(region)) { + color = [1,1,1] + } else { + color = (this.colormapInUse.colormap.get(region) || [255, 255, 255]).map(v => v/255) } + map.set(Number(lblIdx), color) } - - // load preferredFsMode or mode0 by default - const loadMode = this.config.modes.find(m => m.name === preferredFsMode) || this.config.modes[0] - this.loadMode(loadMode) - - this.viewerEvent.emit({ - type: EnumViewerEvt.VIEWERLOADED, - data: true + this.tsRef.applyColorMap(mesh, labelIndices, { + custom: map }) } } @@ -561,59 +480,66 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af return this.handleMouseoverEvent(custEv) } - const evGeom = detail.mesh.geometry - const evVertIdx = detail.mesh.verticesIdicies - const found = this.loadedMeshes.find(({ threeSurfer }) => threeSurfer === evGeom) - if (!found) return this.handleMouseoverEvent(custEv) - - /** - * check if the mesh is toggled off - * if so, do not proceed - */ - const checkKey = this.allKeys.find(key => key.name === found.hemisphere) - if (checkKey && !checkKey.checked) return - - const { hemisphere: key, vIdxArr } = found - - if (!key || !evVertIdx) { - return this.handleMouseoverEvent(custEv) - } + const { + geometry: evGeometry, + // typo in three-surfer + verticesIdicies: evVerticesIndicies, + } = detail.mesh as { geometry: TThreeGeometry, verticesIdicies: number[] } - const labelIdxSet = new Set<number>() - - for (const vIdx of evVertIdx) { - labelIdxSet.add( - vIdxArr[vIdx] - ) - } - if (labelIdxSet.size === 0) { - return this.handleMouseoverEvent(custEv) - } + for (const laterality in this.lateralityMeshRecord) { + const meshRecord = this.lateralityMeshRecord[laterality] + if (meshRecord.mesh !== evGeometry) { + continue + } + /** + * if either labelindex record or colormap record is undefined for this laterality, emit empty event + */ + if (!this.latLblIdxRecord[laterality] || !this.latLblIdxToRegionRecord[laterality]) { + return this.handleMouseoverEvent(custEv) + } + const labelIndexRecord = this.latLblIdxRecord[laterality] + const regionRecord = this.latLblIdxToRegionRecord[laterality] - const hemisphereMap = this.regionMap.get(key) + /** + * check if the mesh is toggled off + * if so, do not proceed + */ + if (!meshRecord.visible) { + return + } - if (!hemisphereMap) { - custEv.regions = Array.from(labelIdxSet).map(v => { - return { - error: `unknown#${v}` + /** + * translate vertex indices to label indicies via set, to remove duplicates + */ + const labelIndexSet = new Set<number>() + for (const idx of evVerticesIndicies){ + const labelOfInterest = labelIndexRecord.labelIndices[idx] + if (!labelOfInterest) { + continue } - }) - return this.handleMouseoverEvent(custEv) - } + labelIndexSet.add(labelOfInterest) + } - custEv.regions = Array.from(labelIdxSet) - .map(lblIdx => { - const ontoR = hemisphereMap.get(lblIdx) - if (ontoR) { - return ontoR - } else { - return { - error: `unkonwn#${lblIdx}` - } + /** + * decode label index to region + */ + if (labelIndexSet.size === 0) { + return this.handleMouseoverEvent(custEv) + } + for (const labelIndex of Array.from(labelIndexSet)) { + if (!regionRecord[labelIndex]) { + custEv.error = `${custEv.error || ''} Cannot decode label index ${labelIndex}` + continue } - }) - return this.handleMouseoverEvent(custEv) + const region = regionRecord[labelIndex] + custEv.regions.push(region) + } + /** + * return handle event + */ + return this.handleMouseoverEvent(custEv) + } } private cameraEv$ = new Subject<{ position: { x: number, y: number, z: number }, zoom: number }>() @@ -624,7 +550,7 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af "@type": 'TViewerInternalStateEmitterEvent', viewerType, payload: { - mode: this.selectedMode, + mode: '', camera: detail.position, hemisphere: 'both' } @@ -633,7 +559,7 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af this.cameraEv$.next(detail) } - ngAfterViewInit(){ + ngAfterViewInit(): void{ const customEvHandler = (ev: CustomEvent) => { const { type, data } = ev.detail if (type === 'mouseover') { @@ -647,53 +573,117 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, OnChanges, Af this.onDestroyCb.push( () => this.domEl.removeEventListener((window as any).ThreeSurfer.CUSTOM_EVENTNAME_UPDATED, customEvHandler) ) + this.tsRef = new (window as any).ThreeSurfer(this.domEl, {highlightHovered: true}) + + this.onDestroyCb.push( + () => { + this.tsRef.dispose() + this.tsRef = null + } + ) + this.tsRef.control.enablePan = false + while (this.tsRefInitCb.length > 0) { + const tsCb = this.tsRefInitCb.pop() + tsCb(this.tsRef) + } + + const meshSub = this.meshLayers$.pipe( + distinctUntilChanged(), + debounceTime(16), + ).subscribe(layers => { + this.loadMeshes(layers) + }) + const vertexIdxSub = this.vertexIndexLayers$.subscribe(layers => this.loadVertexIndexMap(layers)) + const roiSelectedSub = this.selectedRegions$.subscribe(regions => { + this.selectedRegions = regions + this.applyColor() + }) + const colormapSub = this.colormaps$.subscribe(cm => { + this.colormapInUse = cm[0] || null + this.applyColor() + }) + const recordToRegionSub = this.latLblIdxToRegionRecord$.subscribe(val => this.latLblIdxToRegionRecord = val) + const hideDelineationSub = this.store$.pipe( + select(atlasAppearance.selectors.showDelineation) + ).subscribe(flag => { + this.showDelineation = flag + this.applyColor() + /** + * apply color resets mesh visibility + */ + this.updateMeshVisibility() + }) + + this.onDestroyCb.push(() => { + meshSub.unsubscribe() + vertexIdxSub.unsubscribe() + roiSelectedSub.unsubscribe() + colormapSub.unsubscribe() + recordToRegionSub.unsubscribe() + hideDelineationSub.unsubscribe() + }) + + this.viewerEvent.emit({ + type: EnumViewerEvt.VIEWERLOADED, + data: true + }) } public mouseoverText: string private handleMouseoverEvent(ev: THandlingCustomEv){ - const { regions: mouseover, evMesh } = ev + const { regions: mouseover, evMesh, error } = ev this.mouseoverRegions = mouseover this.viewerEvent.emit({ type: EnumViewerEvt.VIEWER_CTX, data: { viewerType: 'threeSurfer', payload: { - fsversion: this.selectedMode, + fsversion: '', faceIndex: evMesh?.faceIndex, vertexIndices: evMesh?.verticesIndicies, position: [], - _mouseoverRegion: mouseover.filter(el => !el.error) + regions: mouseover, + error } } }) - this.mouseoverText = mouseover.length === 0 ? - null : - mouseover.map( - el => el.name || el.error - ).join(' / ') + this.mouseoverText = '' + if (mouseover.length > 0) { + this.mouseoverText += mouseover.map(el => el.name).join(' / ') + } + if (error) { + this.mouseoverText += `::error: ${error}` + } + if (this.mouseoverText === '') this.mouseoverText = null } - public handleCheckBox(key: { name: string, checked: boolean }, flag: boolean){ - const foundMesh = this.loadedMeshes.find(m => m.hemisphere === key.name) - if (!foundMesh) { - throw new Error(`Cannot find mesh with name: ${key.name}`) - } - const meshObj = this.tsRef.customColormap.get(foundMesh.threeSurfer) - if (!meshObj) { - throw new Error(`mesh obj not found!`) + public updateMeshVisibility(): void{ + + for (const key in this.lateralityMeshRecord) { + + const latMeshRecord = this.lateralityMeshRecord[key] + if (!latMeshRecord) { + return + } + const meshObj = this.tsRef.customColormap.get(latMeshRecord.mesh) + if (!meshObj) { + throw new Error(`mesh obj not found!`) + } + meshObj.mesh.visible = latMeshRecord.visible } - meshObj.mesh.visible = flag + } + + switchSurfaceLayer(layer: SapiVolumeModel): void{ + this.store$.dispatch( + actions.selectVolumeById({ + id: layer["@id"] + }) + ) } private onDestroyCb: (() => void) [] = [] - ngOnDestroy() { + ngOnDestroy(): void { while (this.onDestroyCb.length > 0) this.onDestroyCb.pop()() } - - toggleMode(){ - const currIdx = this.modes.findIndex(m => m.name === this.selectedMode) - const newIdx = (currIdx + 1) % this.modes.length - this.loadMode(this.modes[newIdx]) - } } diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.template.html b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.template.html index 31f59c7293be5be213c6634d42722f4cb852bf45..2a9703618ce87100f39bbd6470f88d4cd5bd5a57 100644 --- a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.template.html +++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.template.html @@ -1,5 +1,5 @@ <span *ngIf="mouseoverText" - class="mouseover iv-custom-comp text"> + class="mouseover sxplr-custom-cmp text"> {{ mouseoverText }} </span> @@ -7,8 +7,6 @@ <!-- selector & configurator --> <button mat-icon-button - [iav-key-listener]="[{ type: 'keydown', key: 'q', target: 'document' }]" - (iav-key-event)="toggleMode()" color="primary" class="pe-all" [matMenuTriggerFor]="fsModeSelMenu"> @@ -20,30 +18,27 @@ <!-- selector/configurator menu --> <mat-menu #fsModeSelMenu="matMenu"> - <div class="iv-custom-comp text pl-2 m-2"> - <mat-checkbox *ngFor="let key of allKeys" + <div class="sxplr-custom-cmp text sxplr-pl-2 m-2"> + <mat-checkbox *ngFor="let item of lateralityMeshRecord | keyvalue" class="d-block" iav-stop="click" - (ngModelChange)="handleCheckBox(key, $event)" - [(ngModel)]="key.checked"> - {{ key.name }} + (change)="updateMeshVisibility()" + [(ngModel)]="item.value.visible"> + {{ item.key }} </mat-checkbox> </div> <mat-divider></mat-divider> - <button *ngFor="let mode of modes" + + <button *ngFor="let surfaceLayer of threeSurferSurfaceLayers$ | async" mat-menu-item - (click)="loadMode(mode)" + (click)="switchSurfaceLayer(surfaceLayer)" color="primary"> <mat-icon fontSet="fas" - [fontIcon]="mode.name === selectedMode ? 'fa-circle' : 'fa-none'"> + [fontIcon]="surfaceLayer['@id'] === (selectedSurfaceLayerId$ | async) ? 'fa-circle' : 'fa-none'"> </mat-icon> <span> - {{ mode.name }} + {{ surfaceLayer.metadata.shortName }} </span> - <markdown-dom *ngIf="mode.name === selectedMode" - class="d-inline-block" - markdown="`[q]`"> - </markdown-dom> </button> </mat-menu> diff --git a/src/viewerModule/threeSurfer/types.ts b/src/viewerModule/threeSurfer/types.ts index d6f987870b2f1f6a628f69a0742c8e4177b4b38a..9a15c5642d7c04e5c5057a412532eaabe51be8fe 100644 --- a/src/viewerModule/threeSurfer/types.ts +++ b/src/viewerModule/threeSurfer/types.ts @@ -1,4 +1,4 @@ -import { IContext } from './util' +import { SapiRegionModel } from 'src/atlasComponents/sapi' export type TThreeSurferMesh = { colormap: string @@ -6,20 +6,11 @@ export type TThreeSurferMesh = { hemisphere: 'left' | 'right' } -export type TThreeSurferMode = { - name: string - meshes: TThreeSurferMesh[] -} - -export type TThreeSurferConfig = { - ['@context']: IContext - modes: TThreeSurferMode[] -} - export type TThreeSurferContextInfo = { position: number[] faceIndex: number vertexIndices: number[] fsversion: string - _mouseoverRegion?: any[] + regions: SapiRegionModel[] + error?: string } diff --git a/src/viewerModule/threeSurfer/util.ts b/src/viewerModule/threeSurfer/util.ts index 34a0d85065cf7104d57faf08790dd943804f7b26..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/src/viewerModule/threeSurfer/util.ts +++ b/src/viewerModule/threeSurfer/util.ts @@ -1,14 +0,0 @@ -export interface IContext { - [key: string]: string -} - -export function parseContext(input: string, contexts: IContext[]){ - let output = input - for (const context of contexts) { - for (const key in context) { - const re = new RegExp(`${key}:`, 'g') - output = output.replace(re, context[key]) - } - } - return output -} diff --git a/src/viewerModule/viewer.interface.ts b/src/viewerModule/viewer.interface.ts index 195ee958adbbeb7af67ff27000745d0cc8a67376..cbc85aca0e3e2620a0903b4e332dcb92386e8db0 100644 --- a/src/viewerModule/viewer.interface.ts +++ b/src/viewerModule/viewer.interface.ts @@ -1,4 +1,5 @@ import { EventEmitter } from "@angular/core"; +import { RecursivePartial } from "./nehuba/config.service/type"; import { TNehubaContextInfo } from "./nehuba/types"; import { TThreeSurferContextInfo } from "./threeSurfer/types"; @@ -36,7 +37,7 @@ export interface IViewerCtx { export type TContextArg<K extends keyof IViewerCtx> = ({ viewerType: K - payload: IViewerCtx[K] + payload: RecursivePartial<IViewerCtx[K]> }) export enum EnumViewerEvt { @@ -58,9 +59,6 @@ export type TViewerEvent<T extends keyof IViewerCtx> = TViewerEventViewerLoaded export type TSupportedViewers = keyof IViewerCtx export interface IViewer<K extends keyof IViewerCtx> { - - selectedTemplate: any - selectedParcellation: any viewerCtrlHandler?: IViewerCtrl viewerEvent: EventEmitter<TViewerEvent<K>> } diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.spec.ts b/src/viewerModule/viewerCmp/viewerCmp.component.spec.ts index c67f26f8dcb0be058be3f01c97138a0371dfc4d6..10bb20122d19b73aadd5f7f55b6f065320d055e9 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.spec.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.spec.ts @@ -1,9 +1,6 @@ import { TestBed } from "@angular/core/testing" import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { hot } from "jasmine-marbles" -import { Observable, of, throwError } from "rxjs" -import { viewerStateContextedSelectedRegionsSelector } from "src/services/state/viewerState/selectors" -import { ROIFactory } from "./viewerCmp.component" + describe('> viewerCmp.component.ts', () => { let mockStore: MockStore @@ -15,111 +12,4 @@ describe('> viewerCmp.component.ts', () => { }) mockStore = TestBed.inject(MockStore) }) - describe('> ROIFactory', () => { - const mockDetail = { - foo: 'bar' - } - class MockPCSvc { - getRegionDetail(){ - return of(mockDetail) - } - } - const pcsvc = new MockPCSvc() - let getRegionDetailSpy: jasmine.Spy - - beforeEach(() => { - getRegionDetailSpy = spyOn(pcsvc, 'getRegionDetail') - mockStore.overrideSelector(viewerStateContextedSelectedRegionsSelector, []) - }) - - afterEach(() => { - getRegionDetailSpy.calls.reset() - }) - - describe('> if regoinselected is empty array', () => { - let returnVal: Observable<any> - beforeEach(() => { - getRegionDetailSpy.and.callThrough() - returnVal = ROIFactory(mockStore, pcsvc as any) - }) - it('> returns null', () => { - expect( - returnVal - ).toBeObservable(hot('a', { - a: null - })) - }) - - it('> regionDetail not called', () => { - expect(getRegionDetailSpy).not.toHaveBeenCalled() - }) - }) - - describe('> if regionselected is nonempty', () => { - const mockRegion = { - context: { - template: { - '@id': 'template-id' - }, - parcellation: { - '@id': 'parcellation-id' - }, - atlas: { - '@id': 'atlas-id' - } - }, - ngId: 'foo-bar', - labelIndex: 123 - } - const returnDetail = { - map: { - hello: 'world' - } - } - let returnVal: Observable<any> - beforeEach(() => { - getRegionDetailSpy.and.callFake(() => of(returnDetail)) - mockStore.overrideSelector(viewerStateContextedSelectedRegionsSelector, [mockRegion]) - returnVal = ROIFactory(mockStore, pcsvc as any) - }) - - // TODO check why marble is acting weird - // and that null is not emitted - it('> returns as expected', () => { - expect(returnVal).toBeObservable( - hot('(ab)', { - a: null, - b: { - ...mockRegion, - ...returnDetail - } - }) - ) - const { context } = mockRegion - expect(getRegionDetailSpy).toHaveBeenCalledWith( - context.atlas["@id"], - context.parcellation["@id"], - context.template["@id"], - mockRegion - ) - }) - - it('> if getRegionDetail throws, at least return original region', () => { - getRegionDetailSpy.and.callFake(() => throwError('blabla')) - expect(returnVal).toBeObservable( - hot('(ab)', { - a: null, - b: mockRegion - }) - ) - const { context } = mockRegion - expect(getRegionDetailSpy).toHaveBeenCalledWith( - context.atlas["@id"], - context.parcellation["@id"], - context.template["@id"], - mockRegion - ) - }) - }) - }) }) diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index 5afa255d58cb368762f8e40a4b14f3235a300cae..f0b32efea3c8233a648e0277bfd38ddeaebe2074 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -1,62 +1,20 @@ -import { Subject } from "rxjs" -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentFactory, ComponentFactoryResolver, Inject, Injector, Input, OnDestroy, Optional, TemplateRef, ViewChild, ViewContainerRef } from "@angular/core"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, TemplateRef, ViewChild, ViewContainerRef } from "@angular/core"; import { select, Store } from "@ngrx/store"; -import { combineLatest, merge, NEVER, Observable, of, Subscription } from "rxjs"; -import {catchError, debounceTime, distinctUntilChanged, map, shareReplay, startWith, switchMap, mapTo, filter } from "rxjs/operators"; -import { viewerStateSetSelectedRegions } from "src/services/state/viewerState/actions"; -import { - viewerStateContextedSelectedRegionsSelector, - viewerStateGetSelectedAtlas, - viewerStateSelectedParcellationSelector, - viewerStateSelectedTemplateSelector, - viewerStateStandAloneVolumes, - viewerStateViewerModeSelector -} from "src/services/state/viewerState/selectors" +import { combineLatest, Observable, of, Subscription } from "rxjs"; +import { debounceTime, distinctUntilChanged, map, shareReplay, startWith, switchMap } from "rxjs/operators"; import { CONST, ARIA_LABELS, QUICKTOUR_DESC } from 'common/constants' -import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, REGION_OF_INTEREST } from "src/util/interfaces"; import { animate, state, style, transition, trigger } from "@angular/animations"; import { IQuickTourData } from "src/ui/quickTour"; -import { PureContantService } from "src/util"; import { EnumViewerEvt, TContextArg, TSupportedViewers, TViewerEvent } from "../viewer.interface"; -import { getGetRegionFromLabelIndexId, switchMapWaitFor } from "src/util/fn"; import { ContextMenuService, TContextMenuReg } from "src/contextMenuModule"; -import { ComponentStore } from "../componentStore"; -import { MAT_DIALOG_DATA } from "@angular/material/dialog"; -import { GenericInfoCmp } from "src/atlasComponents/regionalFeatures/bsFeatures/genericInfo"; -import { _PLI_VOLUME_INJ_TOKEN, _TPLIVal } from "src/glue"; -import { uiActionSetPreviewingDatasetFiles } from "src/services/state/uiState.store.helper"; -import { viewerStateSetViewerMode } from "src/services/state/viewerState.store.helper"; import { DialogService } from "src/services/dialogService.service"; -import { RouterService } from "src/routerModule/router.service"; - -type TCStoreViewerCmp = { - overlaySideNav: any -} - -export function ROIFactory(store: Store<any>, svc: PureContantService){ - return store.pipe( - select(viewerStateContextedSelectedRegionsSelector), - switchMap(r => { - if (!r[0]) return of(null) - const { context } = r[0] - const { atlas, template, parcellation } = context || {} - return merge( - of(null), - svc.getRegionDetail(atlas['@id'], parcellation['@id'], template['@id'], r[0]).pipe( - map(det => { - return { - ...r[0], - ...det, - } - }), - // in case detailed requests fails - catchError((_err, _obs) => of(r[0])), - ) - ) - }), - shareReplay(1) - ) -} +import { SAPI, SapiRegionModel } from "src/atlasComponents/sapi"; +import { atlasSelection, userInteraction, } from "src/state"; +import { SapiSpatialFeatureModel, SapiFeatureModel, SapiParcellationModel, SapiSpaceModel } from "src/atlasComponents/sapi/type"; +import { getUuid } from "src/util/fn"; +import { environment } from "src/environments/environment" +import { SapiViewsFeaturesVoiQuery } from "src/atlasComponents/sapiViews/features"; +import { SapiViewsCoreSpaceBoundingBox } from "src/atlasComponents/sapiViews/core"; @Component({ selector: 'iav-cmp-viewer-container', @@ -98,44 +56,26 @@ export function ROIFactory(store: Store<any>, svc: PureContantService){ ]), ], providers: [ - { - provide: REGION_OF_INTEREST, - useFactory: ROIFactory, - deps: [ Store, PureContantService ] - }, - { - provide: OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, - useFactory: (cStore: ComponentStore<TCStoreViewerCmp>) => { - return function overwriteShowDatasetDialog( arg: any ){ - - cStore.setState({ - overlaySideNav: arg - }) - } - }, - deps: [ ComponentStore ] - }, - ComponentStore, DialogService ], changeDetection: ChangeDetectionStrategy.OnPush }) export class ViewerCmp implements OnDestroy { - public _pliTitle = "Fiber structures of a human hippocampus based on joint DMRI, 3D-PLI, and TPFM acquisitions" - public _pliDesc = "The collected datasets provide real multimodal, multiscale structural connectivity insights into the human hippocampus. One post mortem hippocampus was scanned with Anatomical and Diffusion MRI (dMRI) [1], 3D Polarized Light Imaging (3D-PLI) [2], and Two-Photon Fluorescence Microscopy (TPFM) [3] using protocols specifically developed during SGA1 and SGA2, rendering joint tissue imaging possible. MRI scanning was performed with a 11.7 T Preclinical MRI system (gradients: 760 mT/m, slew rate: 9500 T/m/s) yielding T1-w and T2-w maps at 200 µm and dMRI-based maps at 300 µm resolution. During tissue sectioning (60 µm thickness) blockface (en-face) images were acquired from the surface of the frozen brain block, serving as reference for data integration/co-alignment. 530 brain sections were scanned with 3D-PLI. HPC-based image analysis provided transmittance, retardation, and fiber orientation maps at 1.3 µm in-plane resolution. TPFM was finally applied to selected brain sections utilizing autofluorescence properties of the fibrous tissue which appears after PBS washing (MAGIC protocol). The TPFM measurements provide a resolution of 0.44 µm x 0.44 µm x 1 µm." - public _pliLink = "https://doi.org/10.25493/JQ30-E08" - - public _1umTitle = `Cellular level 3D reconstructed volumes at 1µm resolution within the BigBrain occipital cortex` - public _1umDesc = `This dataset contains two 6x6x6 mm3 volumes sampled from the occipital cortex of a human postmortem brain. The volumes were acquired by rescanning the original histological sections of the BigBrain model at an isotropic resolution of 1µm. The sections were linearly 3D reconstructed by aligning matched pairs of bisected cells. Subsequently, both reconstructed stacks were anchored into the 20µm 3D BigBrain space using a 3D similarity transformation.` - public _1umLink = null // `https://search.kg.ebrains.eu/instances/d71d369a-c401-4d7e-b97a-3fb78eed06c5` public CONST = CONST public ARIA_LABELS = ARIA_LABELS + public VOI_QUERY_FLAG = environment.EXPERIMENTAL_FEATURE_FLAG @ViewChild('genericInfoVCR', { read: ViewContainerRef }) genericInfoVCR: ViewContainerRef + @ViewChild('voiFeatures', { read: SapiViewsFeaturesVoiQuery }) + voiQueryDirective: SapiViewsFeaturesVoiQuery + + @ViewChild('bbox', { read: SapiViewsCoreSpaceBoundingBox }) + boundingBoxDirective: SapiViewsCoreSpaceBoundingBox + public quickTourRegionSearch: IQuickTourData = { order: 7, description: QUICKTOUR_DESC.REGION_SEARCH, @@ -145,81 +85,91 @@ export class ViewerCmp implements OnDestroy { description: QUICKTOUR_DESC.ATLAS_SELECTOR, } - @Input() ismobile = false - private subscriptions: Subscription[] = [] private onDestroyCb: (() => void)[] = [] public viewerLoaded: boolean = false - public templateSelected$ = this.store$.pipe( - select(viewerStateSelectedTemplateSelector), - distinctUntilChanged(), + private selectedATP = this.store$.pipe( + atlasSelection.fromRootStore.distinctATP(), + shareReplay(1) + ) + + public selectedAtlas$ = this.selectedATP.pipe( + map(({ atlas }) => atlas) + ) + public templateSelected$ = this.selectedATP.pipe( + map(({ template }) => template) ) - public parcellationSelected$ = this.store$.pipe( - select(viewerStateSelectedParcellationSelector), - distinctUntilChanged(), + public parcellationSelected$ = this.selectedATP.pipe( + map(({ parcellation }) => parcellation) + ) + + public allAvailableParcellations$ = this.store$.pipe( + atlasSelection.fromRootStore.allAvailParcs(this.sapi) ) public selectedRegions$ = this.store$.pipe( - select(viewerStateContextedSelectedRegionsSelector), - distinctUntilChanged(), + select(atlasSelection.selectors.selectedRegions), + ) + + public allAvailableRegions$ = this.store$.pipe( + select(atlasSelection.selectors.selectedParcAllRegions) ) public isStandaloneVolumes$ = this.store$.pipe( - select(viewerStateStandAloneVolumes), + select(atlasSelection.selectors.standaloneVolumes), map(v => v.length > 0) ) public viewerMode$: Observable<string> = this.store$.pipe( - select(viewerStateViewerModeSelector), - shareReplay(1), - ) - - public overlaySidenav$ = this.cStore.select(s => s.overlaySideNav).pipe( + select(atlasSelection.selectors.viewerMode), shareReplay(1), ) public useViewer$: Observable<TSupportedViewers | 'notsupported'> = combineLatest([ - this.templateSelected$, + this.store$.pipe( + atlasSelection.fromRootStore.distinctATP(), + switchMap(({ atlas, template }) => atlas && template + ? this.sapi.getSpace(atlas["@id"], template["@id"]).getVolumes() + : of(null)), + map(vols => { + if (!vols) return null + const flags = { + isNehuba: false, + isThreeSurfer: false + } + if (vols.find(vol => vol.data.volume_type === "neuroglancer/precomputed")) { + flags.isNehuba = true + } + + if (vols.find(vol => vol.data.volume_type === "gii")) { + flags.isThreeSurfer = true + } + return flags + }) + ), this.isStandaloneVolumes$, ]).pipe( - map(([t, isSv]) => { + distinctUntilChanged(([ prevFlags, prevIsSv ], [ currFlags, currIsSv ]) => { + const same = prevIsSv === currIsSv + && prevFlags?.isNehuba === currFlags?.isNehuba + && prevFlags?.isThreeSurfer === currFlags?.isThreeSurfer + return same + }), + map<unknown, TSupportedViewers | 'notsupported'>(([flags, isSv]) => { if (isSv) return 'nehuba' - if (!t) return null - if (!!t['nehubaConfigURL'] || !!t['nehubaConfig']) return 'nehuba' - if (!!t['three-surfer']) return 'threeSurfer' + if (!flags) return null + if (flags.isNehuba) return 'nehuba' + if (flags.isThreeSurfer) return 'threeSurfer' return 'notsupported' - }) + }), + shareReplay(1), ) - public viewerCtx$ = this.viewerModuleSvc.context$ + public viewerCtx$ = this.ctxMenuSvc.context$ - private _1umVoi$ = this.routerSvc.customRoute$.pipe( - map(obj => obj[`x-voi`] === "d71d369a-c401-4d7e-b97a-3fb78eed06c5"), - distinctUntilChanged() - ) - - private voiForceOff = new Subject() - public pliVol$ = merge( - this._pliVol$?.pipe( - filter(arr => arr.length > 0), - mapTo({ - title: this._pliTitle, - description: this._pliDesc, - url: [{ doi: this._pliLink }] - }) - ) || NEVER, - this._1umVoi$.pipe( - filter(flag => flag), - map(() => ({ - title: this._1umTitle, - description: this._1umDesc, - url: this._1umLink - ? [{ doi: this._1umLink }] - : [] - })) - ), - this.voiForceOff + public selectedFeature$: Observable<SapiFeatureModel> = this.store$.pipe( + select(userInteraction.selectors.selectedFeature) ) /** @@ -230,14 +180,12 @@ export class ViewerCmp implements OnDestroy { */ public onlyShowMiniTray$: Observable<boolean> = combineLatest([ this.selectedRegions$, - this.pliVol$.pipe( - startWith(null as { title: string, description: string, url: { doi: string }[] }) - ), this.viewerMode$.pipe( startWith(null as string) ), + this.selectedFeature$, ]).pipe( - map(([ regions, layers, viewerMode ]) => regions.length === 0 && !layers && !viewerMode) + map(([ regions, viewerMode, selectedFeature ]) => regions.length === 0 && !viewerMode && !selectedFeature) ) @ViewChild('viewerStatusCtxMenu', { read: TemplateRef }) @@ -247,57 +195,27 @@ export class ViewerCmp implements OnDestroy { private viewerStatusRegionCtxMenu: TemplateRef<any> public context: TContextArg<TSupportedViewers> - private templateSelected: any - private getRegionFromlabelIndexId: (arg: {labelIndexId: string}) => any + private templateSelected: SapiSpaceModel - private genericInfoCF: ComponentFactory<GenericInfoCmp> - - public clearVoi(){ - this.store$.dispatch( - uiActionSetPreviewingDatasetFiles({ - previewingDatasetFiles: [] - }) - ) - this.routerSvc.setCustomRoute('x-voi', null) - this.voiForceOff.next(false) - } constructor( private store$: Store<any>, - private viewerModuleSvc: ContextMenuService<TContextArg<'threeSurfer' | 'nehuba'>>, - private cStore: ComponentStore<TCStoreViewerCmp>, - cfr: ComponentFactoryResolver, + private ctxMenuSvc: ContextMenuService<TContextArg<'threeSurfer' | 'nehuba'>>, private dialogSvc: DialogService, private cdr: ChangeDetectorRef, - private routerSvc: RouterService, - @Optional() @Inject(_PLI_VOLUME_INJ_TOKEN) private _pliVol$: Observable<_TPLIVal[]>, - @Optional() @Inject(REGION_OF_INTEREST) public regionOfInterest$: Observable<any> + private sapi: SAPI, ){ - this.genericInfoCF = cfr.resolveComponentFactory(GenericInfoCmp) - this.subscriptions.push( - this.selectedRegions$.subscribe(() => { - this.clearPreviewingDataset() - }), - this.viewerModuleSvc.context$.subscribe( + this.ctxMenuSvc.context$.subscribe( (ctx: any) => this.context = ctx ), this.templateSelected$.subscribe( t => this.templateSelected = t ), - this.parcellationSelected$.subscribe( - p => { - this.getRegionFromlabelIndexId = !!p - ? getGetRegionFromLabelIndexId({ parcellation: p }) - : null - } - ), combineLatest([ this.templateSelected$, this.parcellationSelected$, - this.store$.pipe( - select(viewerStateGetSelectedAtlas) - ) + this.selectedAtlas$, ]).pipe( debounceTime(160) ).subscribe(async ([tmpl, parc, atlas]) => { @@ -311,7 +229,7 @@ export class ViewerCmp implements OnDestroy { message.push(`- _${atlas.name}_`) } if (checkPrerelease(tmpl)) { - message.push(`- _${tmpl.name}_`) + message.push(`- _${tmpl.fullName}_`) } if (checkPrerelease(parc)) { message.push(`- _${parc.name}_`) @@ -333,7 +251,7 @@ export class ViewerCmp implements OnDestroy { ) } - ngAfterViewInit(){ + ngAfterViewInit(): void{ const cb: TContextMenuReg<TContextArg<'nehuba' | 'threeSurfer'>> = ({ append, context }) => { /** @@ -356,25 +274,13 @@ export class ViewerCmp implements OnDestroy { let hoveredRegions = [] if (context.viewerType === 'nehuba') { hoveredRegions = (context as TContextArg<'nehuba'>).payload.nehuba.reduce( - (acc, curr) => acc.concat( - curr.labelIndices.map( - lblIdx => { - const labelIndexId = `${curr.layerName}#${lblIdx}` - if (!!this.getRegionFromlabelIndexId) { - return this.getRegionFromlabelIndexId({ - labelIndexId: `${curr.layerName}#${lblIdx}` - }) - } - return labelIndexId - } - ) - ), + (acc, curr) => acc.concat(...curr.regions), [] ) } if (context.viewerType === 'threeSurfer') { - hoveredRegions = (context as TContextArg<'threeSurfer'>).payload._mouseoverRegion + hoveredRegions = (context as TContextArg<'threeSurfer'>).payload.regions } if (hoveredRegions.length > 0) { @@ -390,77 +296,130 @@ export class ViewerCmp implements OnDestroy { return true } - this.viewerModuleSvc.register(cb) + this.ctxMenuSvc.register(cb) this.onDestroyCb.push( - () => this.viewerModuleSvc.deregister(cb) - ) - this.subscriptions.push( - this.overlaySidenav$.pipe( - switchMap(switchMapWaitFor({ - condition: () => !!this.genericInfoVCR - })) - ).subscribe(data => { - if (!this.genericInfoVCR) { - console.warn(`genericInfoVCR not defined!`) - return - } - const injector = Injector.create({ - providers: [{ - provide: MAT_DIALOG_DATA, - useValue: data - }] - }) - - this.genericInfoVCR.clear() - this.genericInfoVCR.createComponent(this.genericInfoCF, null, injector) - this.cdr.markForCheck() - }) + () => this.ctxMenuSvc.deregister(cb) ) } - ngOnDestroy() { + ngOnDestroy(): void { while (this.subscriptions.length) this.subscriptions.pop().unsubscribe() while (this.onDestroyCb.length > 0) this.onDestroyCb.pop()() } - public selectRoi(roi: any) { + public clearRoi(): void{ this.store$.dispatch( - viewerStateSetSelectedRegions({ - selectRegions: [ roi ] - }) + atlasSelection.actions.clearSelectedRegions() ) } - public exitSpecialViewMode(){ + public selectRoi(roi: SapiRegionModel): void { this.store$.dispatch( - viewerStateSetViewerMode({ - payload: null + atlasSelection.actions.selectRegion({ + region: roi }) ) } - public clearPreviewingDataset(){ - /** - * clear all preview - */ - this.cStore.setState({ - overlaySideNav: null - }) + public exitSpecialViewMode(): void{ + this.store$.dispatch( + atlasSelection.actions.clearViewerMode() + ) } - public handleViewerEvent(event: TViewerEvent<'nehuba' | 'threeSurfer'>){ + public handleViewerEvent(event: TViewerEvent<'nehuba' | 'threeSurfer'>): void{ switch(event.type) { case EnumViewerEvt.VIEWERLOADED: this.viewerLoaded = event.data + this.cdr.detectChanges() break case EnumViewerEvt.VIEWER_CTX: - this.viewerModuleSvc.context$.next(event.data) + this.ctxMenuSvc.context$.next(event.data) + if (event.data.viewerType === "nehuba") { + const { nehuba, nav } = (event.data as TContextArg<"nehuba">).payload + if (nehuba) { + const mousingOverRegions = (nehuba || []).reduce((acc, { regions }) => acc.concat(...regions), []) + this.store$.dispatch( + userInteraction.actions.mouseoverRegions({ + regions: mousingOverRegions + }) + ) + } + if (nav) { + this.store$.dispatch( + userInteraction.actions.mouseoverPosition({ + position: { + "@id": getUuid(), + "@type": "https://openminds.ebrains.eu/sands/CoordinatePoint", + coordinates: nav.position.map(p => { + return { + value: p, + } + }), + coordinateSpace: { + '@id': this.templateSelected["@id"] + } + } + }) + ) + } + } break default: } } - public disposeCtxMenu(){ - this.viewerModuleSvc.dismissCtxMenu() + public disposeCtxMenu(): void{ + this.ctxMenuSvc.dismissCtxMenu() + } + + showDataset(feat: SapiFeatureModel): void { + if ((feat as SapiSpatialFeatureModel).location) { + const feature = feat as SapiSpatialFeatureModel + this.store$.dispatch( + atlasSelection.actions.navigateTo({ + navigation: { + orientation: [0, 0, 0, 1], + position: feature.location.center.coordinates.map(v => v.value * 1e6) + }, + animation: true + }) + ) + } + + this.store$.dispatch( + userInteraction.actions.showFeature({ + feature: feat + }) + ) + } + + clearSelectedFeature(): void{ + this.store$.dispatch( + userInteraction.actions.clearShownFeature() + ) + } + + onDismissNonbaseLayer(): void{ + this.store$.dispatch( + atlasSelection.actions.clearNonBaseParcLayer() + ) + } + onSelectParcellation(parcellation: SapiParcellationModel): void{ + this.store$.dispatch( + atlasSelection.actions.selectParcellation({ + parcellation + }) + ) + } + navigateTo(position: number[]): void { + this.store$.dispatch( + atlasSelection.actions.navigateTo({ + navigation: { + position + }, + animation: true, + }) + ) } } diff --git a/src/viewerModule/viewerCmp/viewerCmp.style.css b/src/viewerModule/viewerCmp/viewerCmp.style.css index 6633b28f8ec233a078953ba66fbb6c73877b2274..e7f00472f295d233a6d12cbd1b6dcc751d59d000 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.style.css +++ b/src/viewerModule/viewerCmp/viewerCmp.style.css @@ -3,11 +3,6 @@ position: relative; } -.explore-btn -{ - margin-top: 4.5rem; -} - .region-text-search-autocomplete-position { z-index: 10; @@ -55,7 +50,76 @@ mat-drawer transition: margin-left 200ms cubic-bezier(0.35, 0, 0.25, 1); } -._pli-container +.sapi-container { padding-top: 5rem; } + +sxplr-sapiviews-core-region-region-chip +{ + margin-left:-5rem; +} + +sxplr-sapiviews-core-region-region-chip [prefix] +{ + padding-left:5rem; +} + +.auto-complete-container +{ + display: flex; + flex-direction: row; + align-items: center; +} + +.auto-complete-container > sxplr-sapiviews-core-rich-regionlistsearch +{ + flex: 1 1 0; +} + +.auto-complete-container > button +{ + flex: 0 0 auto; +} + +.min-tray-explr-btn +{ + width: 100%; + padding-left: 0.5rem; + padding-right: 0.5rem; + margin-top: -1rem; +} + +.floating-ui +{ + display: block; + z-index: 5; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + pointer-events: none; +} + +logo-container +{ + height: 2rem; + opacity: 0.2; +} + +mat-list[dense].contextual-block +{ + display: inline-block; + background-color:rgba(200,200,200,0.8); +} + +:host-context([darktheme="true"]) mat-list[dense].contextual-block +{ + background-color : rgba(30,30,30,0.8); +} + +.region-populated +{ + overflow: hidden auto; +} diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index fae280bfbd67128f67fc6567e1fe7b87883aeca2..90d6d4a3810ecb35d68f616af533a3579493d78a 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -1,6 +1,59 @@ -<div class="position-absolute w-100 h-100"> +<div iav-media-query class="position-absolute w-100 h-100" #media="iavMediaQuery"> <ng-container *ngTemplateOutlet="viewerTmpl"> </ng-container> + + <div class="floating-ui"> + + <div *ngIf="(media.mediaBreakPoint$ | async) < 2" + class="fixed-bottom pe-none mb-2 d-flex justify-content-center"> + <logo-container></logo-container> + </div> + + <div *ngIf="(media.mediaBreakPoint$ | async) < 2" floatingMouseContextualContainerDirective> + + <div class="h-0" + iav-mouse-hover + #iavMouseHoverContextualBlock="iavMouseHover"> + </div> + <mat-list dense class="contextual-block"> + <mat-list-item *ngFor="let cvtOutput of iavMouseHoverContextualBlock.currentOnHoverObs$ | async | mouseoverCvt" + class="h-auto"> + + <mat-icon + [fontSet]="cvtOutput.icon.fontSet" + [fontIcon]="cvtOutput.icon.fontIcon" + mat-list-icon> + </mat-icon> + + <div matLine>{{ cvtOutput.text }}</div> + + </mat-list-item> + + <ng-template [ngIf]="voiQueryDirective && (voiQueryDirective.onhover | async)" let-feat> + <mat-list-item> + <mat-icon + fontSet="fas" + fontIcon="fa-database" + mat-list-icon> + </mat-icon> + <div matLine>{{ feat?.metadata?.fullName || 'Feature' }}</div> + </mat-list-item> + </ng-template> + </mat-list> + <!-- TODO Potentially implementing plugin contextual info --> + </div> + + <!-- mouse on click context menu, currently not used --> + <!-- <div class="floating-container" + [attr.aria-label]="CONTEXT_MENU_ARIA_LABEL" + fixedMouseContextualContainerDirective + #fixedContainer="iavFixedMouseCtxContainer"> + + + + </div> --> + + </div> </div> @@ -23,7 +76,7 @@ (@openClose.done)="$event.toState === 'closed' && drawer.close()" [autoFocus]="false" [disableClose]="true" - class="iv-custom-comp darker-bg p-0 pe-all col-10 col-sm-10 col-md-5 col-lg-4 col-xl-3 col-xxl-2 z-index-10"> + class="sxplr-custom-cmp darker-bg sxplr-p-0 pe-all col-10 col-sm-10 col-md-5 col-lg-4 col-xl-3 col-xxl-2 z-index-10"> <!-- entry template --> <ng-template [ngIf]="viewerMode$ | async" let-mode [ngIfElse]="regularTmpl"> @@ -137,58 +190,17 @@ let-drawer="drawer" let-showFullSidenavSwitch="showFullSidenavSwitch"> - <!-- check if preview volume --> - <ng-template [ngIf]="overlaySidenav$ | async" let-overlaySideNav> - - <!-- back btn --> - <button mat-button - (click)="clearPreviewingDataset()" - [attr.aria-label]="ARIA_LABELS.CLOSE" - class="position-absolute z-index-10 m-2"> - <i class="fas fa-chevron-left"></i> - <span class="ml-1"> - Back - </span> - </button> - - <ng-template #genericInfoVCR> - </ng-template> - </ng-template> - - <div [ngClass]="{ - 'invisible overflow-hidden h-0': overlaySidenav$ | async, - 'h-100': !(overlaySidenav$ | async) - }" class="pe-all position-relative d-flex flex-column"> - - - <!-- if pli voi is visible, show pli template - otherwise show region tmpl --> + <!-- selectedFeature || selectedRegion --> <ng-template - [ngTemplateOutlet]="(pliVol$ | async) - ? voiTmpl + [ngTemplateOutlet]="(selectedFeature$ | async) + ? selectedFeatureTmpl : sidenavRegionTmpl" [ngTemplateOutletContext]="{ drawer: drawer, - showFullSidenavSwitch: showFullSidenavSwitch + showFullSidenavSwitch: showFullSidenavSwitch, + feature: selectedFeature$ | async }"> </ng-template> - - <!-- <ng-template let-pliVol [ngIf]="pliVol$ | async" [ngIfElse]="sidenavRegionTmpl"> - <ng-template [ngIf]="pliVol.length > 0" [ngIfElse]="sidenavRegionTmpl"> - <ng-template [ngTemplateOutlet]="voiTmpl"> - - </ng-template> - </ng-template> - </ng-template> --> - - <!-- TODO dataset preview will become deprecated in the future. - Regional feature/data feature will replace it --> - <!-- <div class="hidden" - iav-shown-dataset - #iavShownDataset="iavShownDataset"> - </div> --> - - </div> </ng-template> @@ -206,33 +218,43 @@ 'transition-margin-left': !drawer.opened }"> + <!-- collapsed side bar view --> <div class="h-0 w-100 region-text-search-autocomplete-position"> <ng-container *ngTemplateOutlet="autocompleteTmpl; context: { showTour: true }"> </ng-container> + + <ng-template [ngIf]="VOI_QUERY_FLAG"> + <div *ngIf="!((selectedRegions$ | async)[0])" class="sxplr-p-2 w-100"> + <ng-container *ngTemplateOutlet="spatialFeatureListViewTmpl"></ng-container> + </div> + </ng-template> </div> <!-- such a gross implementation --> <!-- TODO fix this --> - <div class="mt-1-n w-100 pl-2 pr-2 m-1px"> + <div class="min-tray-explr-btn" + sxplr-sapiviews-core-region + [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async" + [sxplr-sapiviews-core-region-template]="templateSelected$ | async" + [sxplr-sapiviews-core-region-parcellation]="parcellationSelected$ | async" + [sxplr-sapiviews-core-region-region]="(selectedRegions$ | async)[0]" + [sxplr-sapiviews-core-region-detail-flag]="true" + #sapiRegion="sapiViewsCoreRegion"> + + <!-- TODO use sapiViews/core/region/base and fix the rest --> <button mat-raised-button *ngIf="!(onlyShowMiniTray$ | async)" [attr.aria-label]="ARIA_LABELS.EXPAND" (click)="showFullSidenav()" - class="explore-btn pe-all w-100" + class="sxplr-mt-9 sxplr-pe-all w-100" [ngClass]="{ - 'darktheme': iavRegion.rgbDarkmode === true, - 'lighttheme': iavRegion.rgbDarkmode === false + 'darktheme': sapiRegion.regionDarkmode, + 'lighttheme': !sapiRegion.regionDarkmode }" - [style.backgroundColor]="iavRegion?.rgbString || 'accent'"> - <span class="text iv-custom-comp"> + [style.backgroundColor]="sapiRegion.regionRgbString"> + <span class="text sxplr-custom-cmp"> Explore </span> - - <div class="hidden" - iav-region - [region]="(selectedRegions$ | async) && (selectedRegions$ | async)[0]" - #iavRegion="iavRegion"> - </div> </button> </div> @@ -243,7 +265,8 @@ <ng-container *ngTemplateOutlet="tabTmpl; context: { isOpen: minTrayVisSwitch.switchState$ | async, regionSelected: selectedRegions$ | async, - click: minTrayVisSwitch.toggle.bind(minTrayVisSwitch) + click: minTrayVisSwitch.toggle.bind(minTrayVisSwitch), + badge: voiQueryDirective && (voiQueryDirective.features$ | async).length || null }"> </ng-container> </div> @@ -287,6 +310,13 @@ <iav-cmp-viewer-nehuba-status *ngIf="(useViewer$ | async) === 'nehuba'" class="pe-all mt-2 muted-7 d-inline-block v-align-top"> </iav-cmp-viewer-nehuba-status> + <button + mat-icon-button + sxplr-share-view + *ngIf="(useViewer$ | async) === 'threeSurfer'" + class="pe-all mt-2 muted-7 d-inline-block v-align-top"> + <i class="fas fa-share-square"></i> + </button> </ng-template> @@ -335,16 +365,16 @@ <!-- signin banner at top right corner --> <top-menu-cmp class="mt-3 mr-2 d-inline-block" - [ismobile]="ismobile" + [ismobile]="(media.mediaBreakPoint$ | async) > 3" [viewerLoaded]="viewerLoaded"> </top-menu-cmp> - <atlas-dropdown-selector - class="v-align-top pt-2 pe-all mt-2 iv-custom-comp bg card m-2 mat-elevation-z2 d-inline-block" + <sxplr-sapiviews-core-atlas-dropdown-selector + class="v-align-top sxplr-pt-2 pe-all mt-2 sxplr-custom-cmp bg card m-2 mat-elevation-z2 d-inline-block" quick-tour [quick-tour-description]="quickTourAtlasSelector.description" [quick-tour-order]="quickTourAtlasSelector.order"> - </atlas-dropdown-selector> + </sxplr-sapiviews-core-atlas-dropdown-selector> </ng-template> @@ -367,18 +397,54 @@ <ng-template #bottomLeftTmpl let-showFullSideNav="showFullSideNav"> <!-- atlas selector --> - <atlas-layer-selector *ngIf="viewerLoaded && !(isStandaloneVolumes$ | async)" - #alSelector="atlasLayerSelector" - class="d-inline-block flex-grow-0 flex-shrink-0 pe-all" - (iav-outsideClick)="alSelector.selectorExpanded = false"> - </atlas-layer-selector> - - <!-- chips --> - <div *ngIf="parcellationSelected$ | async" - class="d-inline-block flex-grow-1 flex-shrink-1 pe-none overflow-x-auto overflow-y-hidden"> - - <viewer-state-breadcrumb class="d-inline-block pe-all" (on-item-click)="showFullSideNav()"> - </viewer-state-breadcrumb> + <sxplr-sapiviews-core-atlas-tmplparcselector + *ngIf="viewerLoaded && !(isStandaloneVolumes$ | async)" + [iav-key-listener]="[{'type': 'keydown', 'key': 'Escape'}]" + (iav-key-event)="tmplParcSelector.closeSelector()" + (iav-outsideClick)="tmplParcSelector.closeSelector()" + #tmplParcSelector="sxplrSapiViewsCoreAtlasTmplparcselector"> + </sxplr-sapiviews-core-atlas-tmplparcselector> + + <!-- scroll container --> + <div class="sxplr-d-inline-flex + sxplr-flex-wrap-nowrap + sxplr-mxw-80vw + sxplr-pe-all + sxplr-of-x-auto + sxplr-of-y-hidden"> + + <!-- selected parcellation chip --> + <sxplr-sapiviews-core-parcellation-smartchip + class="sxplr-z-2 sxplr-mr-1" + [sxplr-sapiviews-core-parcellation-smartchip-parcellation]="parcellationSelected$ | async" + [sxplr-sapiviews-core-parcellation-smartchip-all-parcellations]="allAvailableParcellations$ | async" + (sxplr-sapiviews-core-parcellation-smartchip-dismiss-nonbase-layer)="onDismissNonbaseLayer()" + (sxplr-sapiviews-core-parcellation-smartchip-select-parcellation)="onSelectParcellation($event)" + > + </sxplr-sapiviews-core-parcellation-smartchip> + + <!-- selected region chip --> + <sxplr-sapiviews-core-region-region-chip + *ngFor="let region of selectedRegions$ | async" + class="sxplr-pe-all sxplr-z-1 sxplr-mr-1" + (sxplr-sapiviews-core-region-region-chip-clicked)="showFullSideNav()" + [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async" + [sxplr-sapiviews-core-region-template]="templateSelected$ | async" + [sxplr-sapiviews-core-region-parcellation]="parcellationSelected$ | async" + [sxplr-sapiviews-core-region-region]="region"> + + <div prefix> + </div> + + <div suffix class="sxplr-scale-70"> + <button mat-mini-fab + color="primary" + iav-stop="mousedown click" + (click)="clearRoi()"> + <i class="fas fa-times"></i> + </button> + </div> + </sxplr-sapiviews-core-region-region-chip> </div> </ng-template> @@ -391,20 +457,16 @@ <ng-container [ngSwitch]="useViewer$ | async"> <!-- nehuba viewer --> - <iav-cmp-viewer-nehuba-glue class="d-block w-100 h-100 position-absolute left-0 top-0" + <iav-cmp-viewer-nehuba-glue class="d-block w-100 h-100 position-absolute left-0 tosxplr-p-0" *ngSwitchCase="'nehuba'" (viewerEvent)="handleViewerEvent($event)" - [selectedTemplate]="templateSelected$ | async" - [selectedParcellation]="parcellationSelected$ | async" #iavCmpViewerNehubaGlue="iavCmpViewerNehubaGlue"> </iav-cmp-viewer-nehuba-glue> <!-- three surfer (free surfer viewer) --> - <three-surfer-glue-cmp class="d-block w-100 h-100 position-absolute left-0 top-0" + <three-surfer-glue-cmp class="d-block w-100 h-100 position-absolute left-0 tosxplr-p-0" *ngSwitchCase="'threeSurfer'" - (viewerEvent)="handleViewerEvent($event)" - [selectedTemplate]="templateSelected$ | async" - [selectedParcellation]="parcellationSelected$ | async"> + (viewerEvent)="handleViewerEvent($event)"> </three-surfer-glue-cmp> <!-- if not supported, show not supported message --> @@ -412,7 +474,7 @@ <!-- by default, show splash screen --> <div *ngSwitchDefault> - <ui-splashscreen class="position-absolute left-0 top-0"> + <ui-splashscreen class="position-absolute left-0 tosxplr-p-0"> </ui-splashscreen> </div> </ng-container> @@ -424,15 +486,68 @@ </div> </ng-template> +<!-- region-hierarchy-tmpl --> + +<ng-template #regionHierarchyTmpl> + <div class="sxplr-d-flex sxplr-flex-column sxplr-h-100"> + <sxplr-sapiviews-core-rich-regionshierarchy + class="sxplr-w-100 sxplr-flex-var" + [sxplr-sapiviews-core-rich-regionshierarchy-regions]="allAvailableRegions$ | async" + [sxplr-sapiviews-core-rich-regionshierarchy-accent-regions]="selectedRegions$ | async" + (sxplr-sapiviews-core-rich-regionshierarchy-region-select)="selectRoi($event)" + > + </sxplr-sapiviews-core-rich-regionshierarchy> + + <mat-dialog-actions align="center" class="sxplr-flex-static"> + <button mat-button mat-dialog-close>Close</button> + </mat-dialog-actions> + </div> +</ng-template> <!-- auto complete search box --> <ng-template #autocompleteTmpl let-showTour="showTour"> - <div class="iv-custom-comp bg card ml-2 mr-2 mat-elevation-z8 pe-all"> - <region-text-search-autocomplete class="w-100 pt-2 flex-shrink-0 flex-grow-0"> - </region-text-search-autocomplete> + <div class="sxplr-custom-cmp bg card ml-2 mr-2 mat-elevation-z8 pe-all auto-complete-container"> + + <ng-template #selectedRegionCheckTmpl let-region> + <ng-template #fallbackTmpl> + <button mat-icon-button + class="sxplr-mt-a sxplr-mb-a"> + <i class="far fa-square"></i> + </button> + </ng-template> + + <button *ngIf="selectedRegions$ | async | includes : region; else fallbackTmpl" + mat-icon-button + color="primary" + class="sxplr-mt-a sxplr-mb-a"> + <i class="far fa-check-square"></i> + </button> + </ng-template> + + <sxplr-sapiviews-core-rich-regionlistsearch + [sxplr-sapiviews-core-rich-regionlistsearch-regions]="allAvailableRegions$ | async" + [sxplr-sapiviews-core-rich-regionlistsearch-current-search]="selectedRegions$ | async | getProperty : 0 | getProperty : 'name'" + [sxplr-sapiviews-core-rich-regionlistsearch-region-template-ref]="selectedRegionCheckTmpl" + (sxplr-sapiviews-core-rich-regionlistsearch-region-select)="selectRoi($event)"> + + <button mat-icon-button + search-input-suffix + *ngIf="selectedRegions$ | async | getProperty : 'length'" + (click)="clearRoi()"> + <i class="fas fa-times"></i> + </button> + </sxplr-sapiviews-core-rich-regionlistsearch> + + <button mat-icon-button + color="primary" + [sxplr-dialog]="regionHierarchyTmpl" + sxplr-dialog-size="xl"> + <i class="fas fa-sitemap"></i> + </button> <div class="w-100 h-100 position-absolute pe-none" *ngIf="showTour"> </div> + </div> </ng-template> @@ -441,7 +556,8 @@ let-isOpen="isOpen" let-regionSelected="regionSelected" let-iavAdditionallayers="iavAdditionallayers" - let-click="click"> + let-click="click" + let-badge="badge"> <!-- if mat drawer is open --> <ng-template [ngIf]="isOpen" [ngIfElse]="tabTmpl_closedTmpl"> @@ -449,7 +565,8 @@ [ngTemplateOutletContext]="{ matColor: 'basic', fontIcon: 'fa-chevron-left', - click: click + click: click, + badge: badge }"> </ng-template> </ng-template> @@ -473,18 +590,24 @@ <!-- if region selected > 0 --> <ng-template [ngIf]="regionSelected?.length > 0" [ngIfElse]="tabTmpl_nothingSelected"> - <div class="hidden" - iav-region - [region]="regionSelected[0]" - #tabTmpl_iavRegion="iavRegion"> + + <div sxplr-sapiviews-core-region + [sxplr-sapiviews-core-region-detail-flag]="true" + [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async" + [sxplr-sapiviews-core-region-template]="templateSelected$ | async" + [sxplr-sapiviews-core-region-parcellation]="parcellationSelected$ | async" + [sxplr-sapiviews-core-region-region]="regionSelected[0]" + #tabTmpl_iavRegion="sapiViewsCoreRegion"> + </div> + <!-- TODO fix with sapiView/core/region directive --> <ng-container *ngTemplateOutlet="tabTmpl_defaultTmpl; context: { matColor: 'accent', - customColor: tabTmpl_iavRegion.rgbString, - customColorDarkmode: tabTmpl_iavRegion.rgbDarkmode, + customColor: tabTmpl_iavRegion.regionRgbString, + customColorDarkmode: tabTmpl_iavRegion.regionDarkmode, fontIcon: 'fa-brain', - tooltip: 'Explore ' + tabTmpl_iavRegion.region.name, + tooltip: 'Explore ' + regionSelected[0].name, click: click }"> @@ -497,7 +620,8 @@ matColor: 'primary', fontIcon: 'fa-sitemap', tooltip: 'Explore regions', - click: click + click: click, + badge: badge }"> </ng-container> </ng-template> @@ -531,62 +655,12 @@ [matBadge]="badge" [matBadgeColor]="badgeColor || 'warn'"> - <span [ngClass]="{'iv-custom-comp text': !!customColor}"> + <span [ngClass]="{'sxplr-custom-cmp text': !!customColor}"> <i class="fas" [ngClass]="fontIcon || 'fa-question'"></i> </span> </button> </ng-template> -<!-- VOI sidenav tmpl --> -<ng-template #voiTmpl> - - <!-- back btn --> - <button mat-button - (click)="clearVoi()" - [attr.aria-label]="ARIA_LABELS.CLOSE" - class="position-absolute z-index-10 m-2"> - <i class="fas fa-chevron-left"></i> - <span class="ml-1"> - Back - </span> - </button> - - <mat-card class="_pli-container"> - <mat-card-title> - {{ pliVol$ | async | getProperty : 'title' }} - </mat-card-title> - - <mat-card-subtitle class="d-inline-flex align-items-center flex-wrap"> - <mat-icon fontSet="fas" fontIcon="fa-database"></mat-icon> - <span> - Dataset preview - </span> - - <mat-divider vertical="true" class="ml-2 h-2rem"></mat-divider> - - <a *ngFor="let url of pliVol$ | async | getProperty : 'url'" [href]="url.doi" - mat-icon-button - matTooltip="Explore in EBRAINS Knowledge Graph" - target="_blank"> - <i class="fas fa-external-link-alt"></i> - </a> - - </mat-card-subtitle> - - <small class="d-block text-muted iv-custom-comp darker-bg"> - {{ pliVol$ | async | getProperty : 'description' }} - </small> - - <mat-expansion-panel class="sidenav-cover-header-container"> - <mat-expansion-panel-header> - <mat-panel-title> - Registered Volumes - </mat-panel-title> - </mat-expansion-panel-header> - <layer-browser></layer-browser> - </mat-expansion-panel> - </mat-card> -</ng-template> <!-- region sidenav tmpl --> <ng-template #sidenavRegionTmpl @@ -600,7 +674,7 @@ </ng-container> </div> - <div class="flex-shrink-1 flex-grow-1 d-flex flex-column" + <div class="flex-shrink-1 flex-grow-1 d-flex flex-column sxplr-h-100" [ngClass]="{'region-populated': (selectedRegions$ | async).length > 0 }"> <!-- region detail --> <ng-container *ngIf="selectedRegions$ | async as selectedRegions; else selectRegionErrorTmpl"> @@ -609,8 +683,22 @@ <ng-template [ngIf]="selectedRegions.length === 1" [ngIfElse]="multiRegionWrapperTmpl"> <!-- a series of bugs result in requiring this hacky --> <!-- see https://github.com/HumanBrainProject/interactive-viewer/issues/698 --> - <ng-container *ngTemplateOutlet="singleRegionTmpl; context: { region: (regionOfInterest$ | async) }"> - </ng-container> + + + <ng-template [ngIf]="regionDirective.fetchInProgress$ | async"> + <spinner-cmp class="sxplr-mt-10 fs-200"></spinner-cmp> + </ng-template> + <sxplr-sapiviews-core-region-region-rich + [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async" + [sxplr-sapiviews-core-region-template]="templateSelected$ | async" + [sxplr-sapiviews-core-region-parcellation]="parcellationSelected$ | async" + [sxplr-sapiviews-core-region-region]="selectedRegions[0]" + (sxplr-sapiviews-core-region-region-rich-feature-clicked)="showDataset($event)" + (sxplr-sapiviews-core-region-navigate-to)="navigateTo($event)" + #regionDirective="sapiViewsCoreRegionRich" + > + <div class="sapi-container" header></div> + </sxplr-sapiviews-core-region-region-rich> </ng-template> <!-- multi region wrapper --> @@ -624,8 +712,7 @@ <!-- place holder if length === 0 --> <ng-container *ngIf="selectedRegions.length === 0"> - <ng-container *ngTemplateOutlet="singleRegionTmpl; context: { region: false }"> - </ng-container> + no region selected </ng-container> </ng-container> @@ -642,16 +729,6 @@ </ng-template> -<!-- single region tmpl --> -<ng-template #singleRegionTmpl let-region="region"> - <!-- region detail --> - <region-menu - [region]="region" - class="flex-grow-1 bs-border-box mat-elevation-z4"> - </region-menu> -</ng-template> - - <!-- expansion tmpl --> <ng-template #ngMatAccordionTmpl let-title="title" @@ -660,50 +737,8 @@ let-iconTooltip="iconTooltip" let-iavNgIf="iavNgIf" let-content="content"> - <mat-expansion-panel - [attr.data-opened]="expansionPanel.expanded" - [attr.data-mat-expansion-title]="title" - hideToggle - *ngIf="iavNgIf" - #expansionPanel="matExpansionPanel"> - - <mat-expansion-panel-header> - - <!-- title --> - <mat-panel-title> - {{ title }} - </mat-panel-title> - - <!-- desc + icon --> - <mat-panel-description class="d-flex align-items-center justify-content-end" - [matTooltip]="iconTooltip"> - <span class="mr-3">{{ desc }}</span> - <span class="accordion-icon d-inline-flex justify-content-center"> - <i [class]="iconClass"></i> - </span> - </mat-panel-description> - - </mat-expansion-panel-header> - - <!-- content --> - <ng-template matExpansionPanelContent> - <ng-container *ngTemplateOutlet="content; context: { expansionPanel: expansionPanel }"> - </ng-container> - </ng-template> - </mat-expansion-panel> </ng-template> -<!-- misc dataset (e.g. PLI) --> -<!-- <ng-template #sidenavDsPreviewTmpl let-file="file" let-filename="filename" let-datasetId="datasetId"> - <div class="w-100 flex-grow-1 d-flex flex-column"> - - Previewing misc dataset - - <ng-container *ngTemplateOutlet="collapseBtn"> - </ng-container> - </div> -</ng-template> --> - <!-- select region error... for whatever reason --> <ng-template #selectRegionErrorTmpl> SELECT REGION ERROR @@ -713,41 +748,42 @@ <!-- multi region tmpl --> <ng-template #multiRegionTmpl let-regions="regions"> <ng-template [ngIf]="regions.length > 0" [ngIfElse]="regionPlaceholderTmpl"> - <region-menu - [region]="{ name: CONST.MULTI_REGION_SELECTION }" - class="bs-border-box ml-15px-n mr-15px-n mat-elevation-z4"> - </region-menu> <!-- other regions detail accordion --> <mat-accordion class="bs-border-box ml-15px-n mr-15px-n mt-2"> <!-- Multi regions include --> - <ng-template #multiRegionInclTmpl> - - <mat-chip *ngFor="let r of regions" - iav-region - [region]="r" - class="m-1" - [ngClass]="{ - 'darktheme':regionDirective.rgbDarkmode === true, - 'lighttheme': regionDirective.rgbDarkmode === false - }" - [style.backgroundColor]="regionDirective.rgbString" - #regionDirective="iavRegion"> - <span class="iv-custom-comp text text-truncate d-inline"> - {{ r.name }} - </span> - </mat-chip> - </ng-template> - <ng-container *ngTemplateOutlet="ngMatAccordionTmpl; context: { - title: 'Brain regions', - desc: regions.length, - iconClass: 'fas fa-brain', - iavNgIf: true, - content: multiRegionInclTmpl - }"> - </ng-container> + <mat-expansion-panel + [attr.data-opened]="expansionPanel.expanded" + [attr.data-mat-expansion-title]="'Brain regions'" + hideToggle + #expansionPanel="matExpansionPanel"> + + <mat-expansion-panel-header> + + <!-- title --> + <mat-panel-title> + Brain regions + </mat-panel-title> + + <!-- desc + icon --> + <mat-panel-description class="d-flex align-items-center justify-content-end"> + <span class="mr-3">{{ regions.length }}</span> + <span class="accordion-icon d-inline-flex justify-content-center"> + <i class="fas fa-brain"></i> + </span> + </mat-panel-description> + + </mat-expansion-panel-header> + + <!-- content --> + <ng-template matExpansionPanelContent> + + <!-- TODO use actual region chip in sapiViews/core/region/chip --> + SOMETHING + </ng-template> + </mat-expansion-panel> </mat-accordion> </ng-template> @@ -781,7 +817,7 @@ <!-- context menu template --> <ng-template #viewerCtxMenuTmpl let-tmplRefs="tmplRefs"> - <mat-card class="p-0 d-flex flex-column" + <mat-card class="sxplr-p-0 d-flex flex-column" [iav-key-listener]="[{type: 'keydown', target: 'document', capture: true}]" (iav-key-event)="disposeCtxMenu()" (iav-outsideClick)="disposeCtxMenu()"> @@ -807,6 +843,7 @@ </mat-card> </ng-template> + <!-- viewer status ctx menu --> <ng-template #viewerStatusCtxMenu let-data> <mat-list> @@ -818,7 +855,7 @@ <ng-container *ngSwitchCase="'nehuba'"> <mat-list-item> <span mat-line> - {{ data.context.payload.mouse.real | nmToMm | addUnitAndJoin : '' }} (mm) + {{ data.context.payload.mouse.real | nmToMm | numbers | addUnitAndJoin : '' }} (mm) </span> <span mat-line class="text-muted"> <i class="fas fa-map"></i> @@ -853,27 +890,148 @@ </mat-list> </ng-template> + +<!-- viewer state hover ctx menu --> <ng-template #viewerStatusRegionCtxMenu let-data> <!-- hovered ROIs --> <mat-list> - <mat-list-item *ngFor="let hoveredR of data.metadata.hoveredRegions; let first = first"> + <mat-list-item *ngFor="let region of data.metadata.hoveredRegions; let first = first"> <mat-divider class="top-0" *ngIf="!first"></mat-divider> - <span mat-line> - {{ hoveredR.displayName || hoveredR.name }} - </span> - <span mat-line class="text-muted"> - <i class="fas fa-brain"></i> - <span> - Brain region - </span> - </span> - <!-- lookup region --> - <button mat-icon-button - (click)="selectRoi(hoveredR)" - ctx-menu-dismiss> - <i class="fas fa-search"></i> - </button> + <ng-container *ngTemplateOutlet="viewerStateSapiRegionTmpl; context: { $implicit: region }"> + </ng-container> + </mat-list-item> </mat-list> </ng-template> + + +<!-- sapi region tmpl --> +<ng-template #viewerStateSapiRegionTmpl let-region> + <span mat-line> + {{ region.name }} + </span> + <span mat-line class="text-muted"> + <i class="fas fa-brain"></i> + <span> + Brain region + </span> + </span> + + <!-- lookup region --> + <button mat-icon-button + (click)="selectRoi(region)" + ctx-menu-dismiss> + <i class="fas fa-search"></i> + </button> +</ng-template> + + +<!-- feature tmpls --> +<ng-template #sapiBaseFeatureTmpl + let-backCb="backCb" + let-feature="feature"> + + <sxplr-sapiviews-core-datasets-dataset class="sxplr-z-2 mat-elevation-z2" + [sxplr-sapiviews-core-datasets-dataset-input]="feature"> + + <div header> + <!-- back btn --> + <button mat-button + *ngIf="backCb" + (click)="backCb()" + [attr.aria-label]="ARIA_LABELS.CLOSE" + class="sxplr-mb-2" + > + <i class="fas fa-chevron-left"></i> + <span class="ml-1"> + Back + </span> + </button> + </div> + + </sxplr-sapiviews-core-datasets-dataset> + + <sxplr-sapiviews-features-entry + [sxplr-sapiviews-features-entry-atlas]="selectedAtlas$ | async" + [sxplr-sapiviews-features-entry-space]="templateSelected$ | async" + [sxplr-sapiviews-features-entry-parcellation]="parcellationSelected$ | async" + [sxplr-sapiviews-features-entry-region]="(selectedRegions$ | async)[0]" + [sxplr-sapiviews-features-entry-feature]="feature"> + </sxplr-sapiviews-features-entry> +</ng-template> + +<!-- general feature tmpl --> + +<ng-template let-feature="feature" #selectedFeatureTmpl> + <!-- TODO differentiate between features (spatial, regional etc) --> + + <!-- spatial feature tmpl --> + <ng-container *ngTemplateOutlet="sapiBaseFeatureTmpl; context: { + backCb: clearSelectedFeature.bind(this), + feature: feature + }"> + </ng-container> + + <ng-layer-ctl *ngFor="let vol of feature.volumes" + class="d-block" + [ng-layer-ctl-name]="vol.metadata.fullName" + [ng-layer-ctl-src]="vol.data.url" + [ng-layer-ctl-transform]="vol.data | getProperty : 'detail' | getProperty: 'neuroglancer/precomputed' | getProperty : 'transform'"> + </ng-layer-ctl> + <ng-template #sapiVOITmpl> + </ng-template> + +</ng-template> + +<ng-template #spatialFeatureListViewTmpl> + <div *ngIf="voiQueryDirective && (voiQueryDirective.busy$ | async); else notBusyTmpl" class="fs-200"> + <spinner-cmp></spinner-cmp> + </div> + + <ng-template #notBusyTmpl> + <mat-card *ngIf="voiQueryDirective && (voiQueryDirective.features$ | async).length > 0" class="pe-all mat-elevation-z4"> + <mat-card-title> + Volumes of interest + </mat-card-title> + <mat-card-subtitle class="overflow-hidden"> + <!-- TODO in future, perhaps encapsulate this as a component? seems like a nature fit in sapiView/space/boundingbox --> + <ng-template let-bbox [ngIf]="boundingBoxDirective && (boundingBoxDirective.bbox$ | async | getProperty : 'bbox')" [ngIfElse]="bboxFallbackTmpl"> + Bounding box: {{ bbox[0] | numbers | json }} - {{ bbox[1] | numbers | json }} mm + </ng-template> + <ng-template #bboxFallbackTmpl> + Found nearby + </ng-template> + + </mat-card-subtitle> + + <mat-divider></mat-divider> + + <ng-template [ngIf]="voiQueryDirective"> + + <div *ngFor="let feature of voiQueryDirective.features$ | async" + mat-ripple + (click)="showDataset(feature)" + class="sxplr-custom-cmp hoverable w-100 overflow-hidden text-overflow-ellipses"> + {{ feature.metadata.fullName }} + </div> + </ng-template> + </mat-card> + </ng-template> +</ng-template> + +<div class="d-none" + *ngIf="VOI_QUERY_FLAG" + sxplr-sapiviews-core-space-boundingbox + [sxplr-sapiviews-core-space-boundingbox-atlas]="selectedAtlas$ | async" + [sxplr-sapiviews-core-space-boundingbox-space]="templateSelected$ | async" + [sxplr-sapiviews-core-space-boundingbox-spec]="viewerCtx$ | async | nehubaVCtxToBbox" + #bbox="sxplrSapiViewsCoreSpaceBoundingBox" + sxplr-sapiviews-features-voi-query + [sxplr-sapiviews-features-voi-query-atlas]="selectedAtlas$ | async" + [sxplr-sapiviews-features-voi-query-space]="templateSelected$ | async" + [sxplr-sapiviews-features-voi-query-bbox]="bbox.bbox$ | async | getProperty : 'bbox'" + (sxplr-sapiviews-features-voi-query-onclick)="showDataset($event)" + #voiFeatures="sxplrSapiViewsFeaturesVoiQuery"> + +</div> diff --git a/src/viewerModule/viewerInternalState.service.ts b/src/viewerModule/viewerInternalState.service.ts index 3985a2f65e45663166adfa73e0d1a500adeafcf9..745ff8ecf94620babbb2c1c988366d9f89d03e54 100644 --- a/src/viewerModule/viewerInternalState.service.ts +++ b/src/viewerModule/viewerInternalState.service.ts @@ -33,7 +33,7 @@ export class ViewerInternalStateSvc{ private registeredEmitter: TViewerInternalStateEmitter - applyInternalState<T>(arg: TInteralStatePayload<T>){ + applyInternalState<T>(arg: TInteralStatePayload<T>): void{ if (!this.registeredEmitter) { throw new Error(`No emitter registered. Aborting.`) } @@ -49,7 +49,7 @@ export class ViewerInternalStateSvc{ done: () => this.deregisterEmitter(emitter) } } - deregisterEmitter(emitter: TViewerInternalStateEmitter){ + deregisterEmitter(emitter: TViewerInternalStateEmitter): void{ if (emitter === this.registeredEmitter) { this.viewerInternalState$.next(null) this.registeredEmitter = null diff --git a/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.component.ts b/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.component.ts deleted file mode 100644 index 5a4bea443845687db10084cd253a18ec1c44bb17..0000000000000000000000000000000000000000 --- a/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.component.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { Component, EventEmitter, Output, Pipe, PipeTransform } from "@angular/core"; -import { IQuickTourData } from "src/ui/quickTour"; -import { CONST, ARIA_LABELS, QUICKTOUR_DESC } from 'common/constants' -import { select, Store } from "@ngrx/store"; -import { viewerStateContextedSelectedRegionsSelector, viewerStateGetOverlayingAdditionalParcellations, viewerStateParcVersionSelector, viewerStateSelectedParcellationSelector } from "src/services/state/viewerState/selectors"; -import { distinctUntilChanged, map } from "rxjs/operators"; -import { viewerStateHelperSelectParcellationWithId, viewerStateRemoveAdditionalLayer, viewerStateSetSelectedRegions } from "src/services/state/viewerState.store.helper"; -import { ngViewerActionClearView, ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState.store.helper"; -import { OVERWRITE_SHOW_DATASET_DIALOG_TOKEN } from "src/util/interfaces"; -import { TDatainfosDetail, TSimpleInfo } from "src/util/siibraApiConstants/types"; - -@Component({ - selector: 'viewer-state-breadcrumb', - templateUrl: './breadcrumb.template.html', - styleUrls: [ - './breadcrumb.style.css' - ], - providers: [ - { - provide: OVERWRITE_SHOW_DATASET_DIALOG_TOKEN, - useValue: null - } - ] -}) - -export class ViewerStateBreadCrumb { - - public CONST = CONST - public ARIA_LABELS = ARIA_LABELS - - @Output('on-item-click') - onChipClick = new EventEmitter() - - public quickTourChips: IQuickTourData = { - order: 5, - description: QUICKTOUR_DESC.CHIPS, - } - - public clearViewKeys$ = this.store$.pipe( - select(ngViewerSelectorClearViewEntries) - ) - - public selectedAdditionalLayers$ = this.store$.pipe( - select(viewerStateGetOverlayingAdditionalParcellations), - ) - - public parcellationSelected$ = this.store$.pipe( - select(viewerStateSelectedParcellationSelector), - distinctUntilChanged(), - ) - - public selectedRegions$ = this.store$.pipe( - select(viewerStateContextedSelectedRegionsSelector), - distinctUntilChanged(), - ) - - public selectedLayerVersions$ = this.store$.pipe( - select(viewerStateParcVersionSelector), - map(arr => arr.map(item => { - const overwrittenName = item['@version'] && item['@version']['name'] - return overwrittenName - ? { ...item, displayName: overwrittenName } - : item - })) - ) - - - constructor(private store$: Store<any>){ - - } - - handleChipClick(){ - this.onChipClick.emit(null) - } - - public clearSelectedRegions(){ - this.store$.dispatch( - viewerStateSetSelectedRegions({ - selectRegions: [] - }) - ) - } - - public unsetClearViewByKey(key: string){ - this.store$.dispatch( - ngViewerActionClearView({ payload: { - [key]: false - }}) - ) - } - - public clearAdditionalLayer(layer: { ['@id']: string }){ - this.store$.dispatch( - viewerStateRemoveAdditionalLayer({ - payload: layer - }) - ) - } - - public selectParcellation(parc: any) { - this.store$.dispatch( - viewerStateHelperSelectParcellationWithId({ - payload: parc - }) - ) - } - - public bindFns(fns){ - return () => { - for (const [ fn, ...arg] of fns) { - fn(...arg) - } - } - } - -} - -@Pipe({ - name: 'originalDatainfoPriorityPipe' -}) - -export class OriginalDatainfoPipe implements PipeTransform{ - public transform(arr: (TSimpleInfo | TDatainfosDetail)[]): TDatainfosDetail[]{ - const detailedInfos = arr.filter(item => item['@type'] === 'minds/core/dataset/v1.0.0') as TDatainfosDetail[] - const simpleInfos = arr.filter(item => item['@type'] === 'fzj/tmp/simpleOriginInfo/v0.0.1') as TSimpleInfo[] - - if (detailedInfos.length > 0) return detailedInfos - if (simpleInfos.length > 0) { - return arr.map(d => { - return { - '@type': 'minds/core/dataset/v1.0.0', - name: d.name, - description: d.name, - urls: [], - useClassicUi: false - } - }) - } - return [] - } -} diff --git a/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.template.html b/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.template.html deleted file mode 100644 index a80037b10ee223444d42bfe1f144872b82fa0fa2..0000000000000000000000000000000000000000 --- a/src/viewerModule/viewerStateBreadCrumb/breadcrumb/breadcrumb.template.html +++ /dev/null @@ -1,206 +0,0 @@ -<mat-chip-list - quick-tour - [quick-tour-description]="quickTourChips.description" - [quick-tour-order]="quickTourChips.order"> - - <!-- additional layer --> - - <ng-container> - <ng-container *ngTemplateOutlet="currParcellationTmpl; context: { - addParc: (selectedAdditionalLayers$ | async), - parc: (parcellationSelected$ | async) - }"> - </ng-container> - </ng-container> - - <!-- any selected region(s) --> - <ng-container> - <ng-container *ngTemplateOutlet="selectedRegionTmpl"> - </ng-container> - </ng-container> -</mat-chip-list> - - -<!-- parcellation chip / region chip --> -<ng-template #currParcellationTmpl let-parc="parc" let-addParc="addParc"> - <div [matMenuTriggerFor]="layerVersionMenu" - [matMenuTriggerData]="{ layerVersionMenuTrigger: layerVersionMenuTrigger }" - #layerVersionMenuTrigger="matMenuTrigger"> - - <ng-template [ngIf]="addParc.length > 0" [ngIfElse]="defaultParcTmpl"> - <ng-container *ngFor="let p of addParc"> - <ng-container *ngTemplateOutlet="chipTmpl; context: { - parcel: p, - selected: true, - dismissable: true, - ariaLabel: ARIA_LABELS.PARC_VER_SELECT, - onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) - }"> - </ng-container> - </ng-container> - </ng-template> - <ng-template #defaultParcTmpl> - <ng-template [ngIf]="parc"> - - <ng-container *ngTemplateOutlet="chipTmpl; context: { - parcel: parc, - selected: false, - dismissable: false, - ariaLabel: ARIA_LABELS.PARC_VER_SELECT, - onclick: layerVersionMenuTrigger.toggleMenu.bind(layerVersionMenuTrigger) - }"> - </ng-container> - </ng-template> - </ng-template> - </div> -</ng-template> - - -<ng-template #selectedRegionTmpl> - - <!-- regions chip --> - <ng-template [ngIf]="selectedRegions$ | async" let-selectedRegions="ngIf"> - <!-- if regions.length > 1 --> - <!-- use group chip --> - <ng-template [ngIf]="selectedRegions.length > 1" [ngIfElse]="singleRegionChipTmpl"> - <mat-chip - color="primary" - selected - (click)="handleChipClick()" - class="pe-all position-relative z-index-1 ml-8-n"> - <span class="iv-custom-comp text text-truncate d-inline pl-4"> - {{ CONST.MULTI_REGION_SELECTION }} - </span> - <mat-icon - (click)="clearSelectedRegions()" - fontSet="fas" - iav-stop="click" - fontIcon="fa-times"> - </mat-icon> - </mat-chip> - </ng-template> - - <!-- if reginos.lengt === 1 --> - <!-- use single region chip --> - <ng-template #singleRegionChipTmpl> - <ng-container *ngFor="let r of selectedRegions"> - - <!-- region chip for discrete map --> - <mat-chip - (click)="handleChipClick()" - [region]="r" - class="pe-all position-relative z-index-1 ml-8-n" - [ngClass]="{ - 'darktheme':regionDirective.rgbDarkmode === true, - 'lighttheme': regionDirective.rgbDarkmode === false - }" - [style.backgroundColor]="regionDirective.rgbString" - iav-region - #regionDirective="iavRegion"> - <span class="iv-custom-comp text text-truncate d-inline pl-4"> - {{ r.name }} - </span> - <mat-icon - class="iv-custom-comp text" - (click)="clearSelectedRegions()" - fontSet="fas" - iav-stop="click" - fontIcon="fa-times"> - </mat-icon> - </mat-chip> - - <!-- chips for previewing origin datasets/continous map --> - <ng-container *ngFor="let originDataset of (r.originDatasets || []); let index = index"> - - <mat-chip *ngFor="let key of clearViewKeys$ | async" - (click)="handleChipClick()" - class="pe-all position-relative ml-8-n"> - <span class="pl-4"> - {{ key }} - </span> - <mat-icon (click)="unsetClearViewByKey(key)" - fontSet="fas" - iav-stop="click" - fontIcon="fa-times"> - - </mat-icon> - </mat-chip> - </ng-container> - - </ng-container> - </ng-template> - </ng-template> - -</ng-template> - -<!-- layer version selector --> -<mat-menu #layerVersionMenu - class="bg-none box-shadow-none" - [aria-label]="ARIA_LABELS.PARC_VER_CONTAINER" - [hasBackdrop]="false"> - <ng-template matMenuContent let-layerVersionMenuTrigger="layerVersionMenuTrigger"> - <div (iav-outsideClick)="layerVersionMenuTrigger.closeMenu()"> - <ng-container *ngFor="let parcVer of selectedLayerVersions$ | async"> - <ng-container *ngIf="parcellationSelected$ | async as selectedParcellation"> - - <ng-container *ngTemplateOutlet="chipTmpl; context: { - parcel: parcVer, - selected: selectedParcellation['@id'] === parcVer['@id'], - dismissable: false, - class: 'w-100', - ariaLabel: parcVer.displayName || parcVer.name, - onclick: bindFns([ - [ selectParcellation.bind(this), parcVer ], - [ layerVersionMenuTrigger.closeMenu.bind(layerVersionMenuTrigger) ] - ]) - }"> - </ng-container> - </ng-container> - <div class="mt-1"></div> - </ng-container> - </div> - </ng-template> -</mat-menu> - - -<ng-template #chipTmpl - let-parcel="parcel" - let-selected="selected" - let-dismissable="dismissable" - let-chipClass="class" - let-ariaLabel="ariaLabel" - let-onclick="onclick"> - <mat-chip class="pe-all position-relative z-index-2 d-inline-flex justify-content-between" - [ngClass]="chipClass" - [attr.aria-label]="ariaLabel" - (click)="onclick && onclick()" - [selected]="selected"> - - <span class="ws-no-wrap"> - {{ parcel?.groupName ? (parcel?.groupName + ' - ') : '' }}{{ parcel && (parcel.displayName || parcel.name) }} - </span> - - <!-- info icon --> - <ng-container *ngFor="let originDatainfo of (parcel.originDatainfos | originalDatainfoPriorityPipe)"> - - <mat-icon - fontSet="fas" - fontIcon="fa-info-circle" - iav-stop="click" - iav-dataset-show-dataset-dialog - [iav-dataset-show-dataset-dialog-name]="originDatainfo.name" - [iav-dataset-show-dataset-dialog-description]="originDatainfo.description" - [iav-dataset-show-dataset-dialog-urls]="originDatainfo.urls"> - </mat-icon> - - </ng-container> - - <!-- dismiss icon --> - <mat-icon - *ngIf="dismissable" - (click)="clearAdditionalLayer(parcel); $event.stopPropagation()" - fontSet="fas" - fontIcon="fa-times"> - </mat-icon> - </mat-chip> -</ng-template> diff --git a/src/viewerModule/viewerStateBreadCrumb/module.ts b/src/viewerModule/viewerStateBreadCrumb/module.ts deleted file mode 100644 index ba53571ed934fe530330bd5b96fa2064484151e3..0000000000000000000000000000000000000000 --- a/src/viewerModule/viewerStateBreadCrumb/module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; -import { ParcellationRegionModule } from "src/atlasComponents/parcellationRegion"; -import { KgDatasetModule } from "src/atlasComponents/regionalFeatures/bsFeatures/kgDataset"; -import { QuickTourModule } from "src/ui/quickTour"; -import { AngularMaterialModule } from "src/sharedModules"; -import { UtilModule } from "src/util"; -import { OriginalDatainfoPipe, ViewerStateBreadCrumb } from "./breadcrumb/breadcrumb.component"; - -@NgModule({ - imports: [ - CommonModule, - AngularMaterialModule, - QuickTourModule, - ParcellationRegionModule, - KgDatasetModule, - UtilModule, - ], - declarations: [ - ViewerStateBreadCrumb, - OriginalDatainfoPipe, - ], - exports: [ - ViewerStateBreadCrumb, - ], - providers:[ - ] -}) - -export class ViewerStateBreadCrumbModule{} \ No newline at end of file diff --git a/src/widget/constants.ts b/src/widget/constants.ts index e4070494f940a01fb0dce8874eba63a665c1bdfd..a779b079ad01afafeff6bf9e52f0675e4fdad6e4 100644 --- a/src/widget/constants.ts +++ b/src/widget/constants.ts @@ -20,3 +20,6 @@ interface TypeActionWidgetReturnVal<T>{ export type TypeActionToWidget<T> = (type: EnumActionToWidget, obj: T, option: IActionWidgetOption) => TypeActionWidgetReturnVal<T> +export const WIDGET_PORTAL_TOKEN = new InjectionToken<Record<string, unknown>>("WIDGET_PORTAL_TOKEN") + +export const RM_WIDGET = new InjectionToken('RM_WIDGET') \ No newline at end of file diff --git a/src/widget/index.ts b/src/widget/index.ts index c322c25f3d4ca885b9812367b0e55202a67da575..1cabfcd56301fe9cc9cc32103a33e3db51d49c62 100644 --- a/src/widget/index.ts +++ b/src/widget/index.ts @@ -1,4 +1,4 @@ export { WidgetModule } from './widget.module' -export { WidgetUnit } from './widgetUnit/widgetUnit.component' -export { IWidgetOptionsInterface, WidgetServices } from './widgetService.service' +export { WidgetPortal } from "./widgetPortal/widgetPortal.component" +export { WidgetService } from "./service" export { EnumActionToWidget, TypeActionToWidget, IActionWidgetOption } from './constants' diff --git a/src/widget/service.ts b/src/widget/service.ts new file mode 100644 index 0000000000000000000000000000000000000000..9430269418731ceb3b57810b54b26ef155ef7ef0 --- /dev/null +++ b/src/widget/service.ts @@ -0,0 +1,56 @@ +import { ComponentPortal } from "@angular/cdk/portal"; +import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, Injector, ViewContainerRef } from "@angular/core"; +import { RM_WIDGET } from "./constants"; +import { WidgetPortal } from "./widgetPortal/widgetPortal.component"; + +@Injectable({ + providedIn: 'root' +}) + +export class WidgetService { + + public vcr: ViewContainerRef + + private viewRefMap = new Map<WidgetPortal<unknown>, ComponentRef<WidgetPortal<unknown>>>() + private cf: ComponentFactory<WidgetPortal<unknown>> + + constructor(cfr: ComponentFactoryResolver){ + this.cf = cfr.resolveComponentFactory(WidgetPortal) + } + + public addNewWidget<T>(Component: new (...arg: any) => T, injector: Injector): WidgetPortal<T> { + const inj = Injector.create({ + providers: [{ + provide: RM_WIDGET, + useValue: (cmp: WidgetPortal<T>) => this.rmWidget(cmp) + }], + parent: injector + }) + const widgetPortal = this.vcr.createComponent(this.cf, 0, inj) as ComponentRef<WidgetPortal<T>> + const cmpPortal = new ComponentPortal<T>(Component, this.vcr, inj) + + this.viewRefMap.set(widgetPortal.instance, widgetPortal) + + widgetPortal.instance.portal = cmpPortal + return widgetPortal.instance + } + + public rmWidget(wdg: WidgetPortal<unknown>) { + + /** + * if wdg no longer exist in viewRefMap, it should already been deleted. + */ + if (!this.viewRefMap.has(wdg)) { + return + } + const hostView = this.viewRefMap.get(wdg).hostView + + this.viewRefMap.delete(wdg) + + const idx = this.vcr.indexOf(hostView) + if (idx < 0) { + console.warn(`idx less than 0, cannot remove`) + } + this.vcr.remove(idx) + } +} diff --git a/src/widget/widget.module.ts b/src/widget/widget.module.ts index dd6b7960a9dfd4fbe32aa66b617f12a73a547df6..138ed66482a78ad917959b416aca333c415c09b0 100644 --- a/src/widget/widget.module.ts +++ b/src/widget/widget.module.ts @@ -1,27 +1,29 @@ import { NgModule } from "@angular/core"; -import { WidgetUnit } from "./widgetUnit/widgetUnit.component"; -import { WidgetServices } from "./widgetService.service"; -import { AngularMaterialModule } from "src/sharedModules"; import { CommonModule } from "@angular/common"; import { ComponentsModule } from "src/components"; +import { WidgetCanvas } from "./widgetCanvas.directive"; +import { WidgetPortal } from "./widgetPortal/widgetPortal.component"; +import { MatCardModule } from "@angular/material/card"; +import { DragDropModule } from "@angular/cdk/drag-drop"; +import { MatButtonModule } from "@angular/material/button"; +import { PortalModule } from "@angular/cdk/portal"; @NgModule({ imports:[ - AngularMaterialModule, + MatCardModule, + DragDropModule, + MatButtonModule, + PortalModule, CommonModule, ComponentsModule, ], declarations: [ - WidgetUnit - ], - entryComponents: [ - WidgetUnit - ], - providers: [ - WidgetServices, + WidgetCanvas, + WidgetPortal, ], + providers: [], exports: [ - WidgetUnit + WidgetCanvas, ] }) diff --git a/src/widget/widgetCanvas.directive.ts b/src/widget/widgetCanvas.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..206548386ac1bb178d7987b47390aeba95bcf016 --- /dev/null +++ b/src/widget/widgetCanvas.directive.ts @@ -0,0 +1,15 @@ +import { Directive, ViewContainerRef } from "@angular/core"; +import { WidgetService } from "./service"; + +@Directive({ + selector: `[widget-canvas]` +}) + +export class WidgetCanvas { + constructor( + wSvc: WidgetService, + vcr: ViewContainerRef, + ){ + wSvc.vcr = vcr + } +} diff --git a/src/widget/widgetPortal/widgetPortal.component.ts b/src/widget/widgetPortal/widgetPortal.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..5e5cc05fa5fa6f15a139fb415097c3fff4078c48 --- /dev/null +++ b/src/widget/widgetPortal/widgetPortal.component.ts @@ -0,0 +1,41 @@ +import { ComponentPortal } from "@angular/cdk/portal"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Optional } from "@angular/core"; +import { RM_WIDGET } from "../constants"; + +@Component({ + selector: 'sxplr-widget-portal', + templateUrl: './widgetPortal.template.html', + styleUrls: [ + './widgetPortal.style.css' + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) + +export class WidgetPortal<T>{ + + portal: ComponentPortal<T> + + private _name: string + get name() { + return this._name + } + set name(val) { + this._name = val + this.cdr.markForCheck() + } + + defaultPosition = { + x: 200, + y: 200, + } + + constructor( + private cdr: ChangeDetectorRef, + @Optional() @Inject(RM_WIDGET) private rmWidget: (inst: unknown) => void + ){ + + } + exit(){ + if (this.rmWidget) this.rmWidget(this) + } +} diff --git a/src/widget/widgetPortal/widgetPortal.style.css b/src/widget/widgetPortal/widgetPortal.style.css new file mode 100644 index 0000000000000000000000000000000000000000..12e9c809623d27f95eef3986c869ed9966496328 --- /dev/null +++ b/src/widget/widgetPortal/widgetPortal.style.css @@ -0,0 +1,56 @@ +:host +{ + pointer-events: none; + display: block; + max-width: 24rem; +} + +mat-card +{ + pointer-events: all; + max-width: 36vw; + height: 36rem; + max-height: 90vh; +} + +mat-card-content +{ + height: 100%; + width: 100%; + display: flex; + flex-direction: column; +} + +.widget-portal-header +{ + display: flex; + justify-content: space-between; + align-items: center; +} + +.widget-portal-content +{ + flex-grow: 1; +} + +.hover-grab +{ + opacity: 0.5; + transition: opacity 200ms ease-in-out; + cursor: move; +} + +.hover-grab:hover +{ + opacity: 1.0; +} + +.widget-grab-handle +{ + margin-right:1rem; +} + +.widget-name +{ + flex-grow: 1; +} diff --git a/src/widget/widgetPortal/widgetPortal.template.html b/src/widget/widgetPortal/widgetPortal.template.html new file mode 100644 index 0000000000000000000000000000000000000000..7aff890d9ecab44127f5b70a0df3a5ba0f5c305c --- /dev/null +++ b/src/widget/widgetPortal/widgetPortal.template.html @@ -0,0 +1,22 @@ +<mat-card cdkDrag [cdkDragFreeDragPosition]="defaultPosition"> + <mat-card-content> + <div class="widget-portal-header" cdkDragHandle> + <span class="hover-grab widget-grab-handle"> + <i class="fas fa-grip-vertical"></i> + </span> + + <span *ngIf="name" class="widget-name"> + {{ name }} + </span> + + <button mat-icon-button (click)="exit()"> + <i class="fas fa-times"></i> + </button> + </div> + + <div class="widget-portal-content"> + <ng-template [cdkPortalOutlet]="portal"> + </ng-template> + </div> + </mat-card-content> +</mat-card> diff --git a/src/widget/widgetService.service.ts b/src/widget/widgetService.service.ts deleted file mode 100644 index 89d1989613a35c1973b892a1627abe2c32361d51..0000000000000000000000000000000000000000 --- a/src/widget/widgetService.service.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, Injector, OnDestroy, ViewContainerRef } from "@angular/core"; -import { BehaviorSubject, Subscription } from "rxjs"; -import { LoggingService } from "src/logging"; -import { WidgetUnit } from "./widgetUnit/widgetUnit.component"; - -@Injectable({ - providedIn : 'root', -}) - -export class WidgetServices implements OnDestroy { - - public floatingContainer: ViewContainerRef - public dockedContainer: ViewContainerRef - public factoryContainer: ViewContainerRef - - private widgetUnitFactory: ComponentFactory<WidgetUnit> - private widgetComponentRefs: Set<ComponentRef<WidgetUnit>> = new Set() - - private clickedListener: Subscription[] = [] - - public minimisedWindow$: BehaviorSubject<Set<WidgetUnit>> - private minimisedWindow: Set<WidgetUnit> = new Set() - - constructor( - private cfr: ComponentFactoryResolver, - private injector: Injector, - private log: LoggingService, - ) { - this.widgetUnitFactory = this.cfr.resolveComponentFactory(WidgetUnit) - this.minimisedWindow$ = new BehaviorSubject(this.minimisedWindow) - } - - private subscriptions: Subscription[] = [] - - - public ngOnDestroy() { - while (this.subscriptions.length > 0) { - this.subscriptions.pop().unsubscribe() - } - } - - public clearAllWidgets() { - [...this.widgetComponentRefs].forEach((cr: ComponentRef<WidgetUnit>) => { - if (!cr.instance.persistency) { cr.destroy() } - }) - - this.clickedListener.forEach(s => s.unsubscribe()) - } - - public rename(wu: WidgetUnit, {title, titleHTML}: {title: string, titleHTML: string}) { - /** - * WARNING: always sanitize before pass to rename fn! - */ - wu.title = title - wu.titleHTML = titleHTML - } - - public minimise(wu: WidgetUnit) { - this.minimisedWindow.add(wu) - this.minimisedWindow$.next(new Set(this.minimisedWindow)) - } - - public isMinimised(wu: WidgetUnit) { - return this.minimisedWindow.has(wu) - } - - public unminimise(wu: WidgetUnit) { - this.minimisedWindow.delete(wu) - this.minimisedWindow$.next(new Set(this.minimisedWindow)) - } - - public addNewWidget(guestComponentRef: ComponentRef<any>, options?: Partial<IWidgetOptionsInterface>): ComponentRef<WidgetUnit> { - const component = this.widgetUnitFactory.create(this.injector) - const _option = getOption(options) - - // TODO bring back docked state? - _option.state = 'floating' - - _option.state === 'floating' - ? this.floatingContainer.insert(component.hostView) - : _option.state === 'docked' - ? this.dockedContainer.insert(component.hostView) - : this.floatingContainer.insert(component.hostView) - - if (component.constructor === Error) { - throw component - } else { - const _component = (component as ComponentRef<WidgetUnit>) - - // guestComponentRef - // insert view - _component.instance.container.insert( guestComponentRef.hostView ) - // on host destroy, destroy guest - _component.onDestroy(() => guestComponentRef.destroy()) - - /* programmatic DI */ - _component.instance.widgetServices = this - - /* common properties */ - _component.instance.state = _option.state - _component.instance.exitable = _option.exitable - _component.instance.title = _option.title - _component.instance.persistency = _option.persistency - _component.instance.titleHTML = _option.titleHTML - - /* internal properties, used for changing state */ - _component.instance.guestComponentRef = guestComponentRef - - if (_option.state === 'floating') { - let position = [400, 100] as [number, number] - while ([...this.widgetComponentRefs].some(widget => - widget.instance.state === 'floating' && - widget.instance.position.every((v, idx) => v === position[idx]))) { - position = position.map(v => v + 10) as [number, number] - } - _component.instance.position = position - } - - /* set width and height. or else floating components will obstruct viewers */ - _component.instance.setWidthHeight() - - this.widgetComponentRefs.add( _component ) - _component.onDestroy(() => this.minimisedWindow.delete(_component.instance)) - - this.clickedListener.push( - _component.instance.clickedEmitter.subscribe((widgetUnit: WidgetUnit) => { - /** - * TODO this operation - */ - if (widgetUnit.state !== 'floating') { - return - } - const foundWidgetCompRef = [...this.widgetComponentRefs].find(wr => wr.instance === widgetUnit) - if (!foundWidgetCompRef) { - return - } - const idx = this.floatingContainer.indexOf(foundWidgetCompRef.hostView) - if (idx === this.floatingContainer.length - 1 ) { - return - } - this.floatingContainer.detach(idx) - this.floatingContainer.insert(foundWidgetCompRef.hostView) - }), - ) - - return _component - } - } - - public changeState(widgetUnit: WidgetUnit, options: IWidgetOptionsInterface) { - const widgetRef = [...this.widgetComponentRefs].find(cr => cr.instance === widgetUnit) - if (widgetRef) { - this.widgetComponentRefs.delete(widgetRef) - widgetRef.instance.container.detach( 0 ) - const guestComopnent = widgetRef.instance.guestComponentRef - this.addNewWidget(guestComopnent, options) - - widgetRef.destroy() - } else { - this.log.warn('widgetref not found') - } - } - - public exitWidget(widgetUnit: WidgetUnit) { - const widgetRef = [...this.widgetComponentRefs].find(cr => cr.instance === widgetUnit) - if (widgetRef) { - widgetRef.destroy() - this.widgetComponentRefs.delete(widgetRef) - } else { - this.log.warn('widgetref not found') - } - } - - public dockAllWidgets() { - /* nb cannot directly iterate the set, as the set will be updated and create and infinite loop */ - [...this.widgetComponentRefs].forEach(cr => cr.instance.dock()) - } - - public floatAllWidgets() { - [...this.widgetComponentRefs].forEach(cr => cr.instance.undock()) - } -} - -function safeGetSingle(obj: any, arg: string) { - return typeof obj === 'object' && obj !== null && typeof arg === 'string' - ? obj[arg] - : null -} - -function safeGet(obj: any, ...args: string[]) { - let _obj = Object.assign({}, obj) - while (args.length > 0) { - const arg = args.shift() - _obj = safeGetSingle(_obj, arg) - } - return _obj -} - -function getOption(option?: Partial<IWidgetOptionsInterface>): IWidgetOptionsInterface { - return{ - exitable : safeGet(option, 'exitable') !== null - ? safeGet(option, 'exitable') - : true, - state : safeGet(option, 'state') || 'floating', - title : safeGet(option, 'title') || 'Untitled', - persistency : safeGet(option, 'persistency') || false, - titleHTML: safeGet(option, 'titleHTML') || null, - } -} - -export interface IWidgetOptionsInterface { - title?: string - state?: 'docked' | 'floating' - exitable?: boolean - persistency?: boolean - titleHTML?: string -} diff --git a/src/widget/widgetUnit/widgetUnit.component.ts b/src/widget/widgetUnit/widgetUnit.component.ts deleted file mode 100644 index e77afb49abca1ac73c26ef58ee37e34be53a2d03..0000000000000000000000000000000000000000 --- a/src/widget/widgetUnit/widgetUnit.component.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { Component, ComponentRef, EventEmitter, HostBinding, HostListener, Input, OnDestroy, OnInit, Output, ViewChild, ViewContainerRef } from "@angular/core"; - -import { Observable, Subscription } from "rxjs"; -import { map } from "rxjs/operators"; -import { WidgetServices } from "../widgetService.service"; - -@Component({ - templateUrl : './widgetUnit.template.html', - styleUrls : [ - `./widgetUnit.style.css`, - ], -}) - -export class WidgetUnit implements OnInit, OnDestroy { - @ViewChild('container', {read: ViewContainerRef, static: true}) public container: ViewContainerRef - - @HostBinding('attr.state') - public state: 'docked' | 'floating' = 'docked' - - @HostBinding('style.width') - public width: string = this.state === 'docked' ? null : '0px' - - @HostBinding('style.height') - public height: string = this.state === 'docked' ? null : '0px' - - @HostBinding('style.display') - public isMinimised: string - - public isMinimised$: Observable<boolean> - - public hoverableConfig = { - translateY: -1, - } - - /** - * Timed alternates of blinkOn property should result in attention grabbing blink behaviour - */ - private _blinkOn: boolean = false - get blinkOn() { - return this._blinkOn - } - - set blinkOn(val: boolean) { - this._blinkOn = !!val - } - - get showProgress() { - return this.progressIndicator !== null - } - - /** - * Some plugins may like to show progress indicator for long running processes - * If null, no progress is running - * This value should be between 0 and 1 - */ - private _progressIndicator: number = null - get progressIndicator() { - return this._progressIndicator - } - - set progressIndicator(val: number) { - if (isNaN(val)) { - this._progressIndicator = null - return - } - if (val < 0) { - this._progressIndicator = 0 - return - } - if (val > 1) { - this._progressIndicator = 1 - return - } - this._progressIndicator = val - } - - public canBeDocked: boolean = false - @HostListener('mousedown') - public clicked() { - this.clickedEmitter.emit(this) - this.blinkOn = false - } - - @Input() public title: string = 'Untitled' - - @Output() - public clickedEmitter: EventEmitter<WidgetUnit> = new EventEmitter() - - @Input() - public exitable: boolean = true - - @Input() - public titleHTML: string = null - - public guestComponentRef: ComponentRef<any> - public widgetServices: WidgetServices - public cf: ComponentRef<WidgetUnit> - private subscriptions: Subscription[] = [] - - public id: string - constructor() { - this.id = Date.now().toString() - } - - public ngOnInit() { - this.canBeDocked = typeof this.widgetServices.dockedContainer !== 'undefined' - - this.isMinimised$ = this.widgetServices.minimisedWindow$.pipe( - map(set => set.has(this)), - ) - this.subscriptions.push( - this.isMinimised$.subscribe(flag => this.isMinimised = flag ? 'none' : null), - ) - } - - public ngOnDestroy() { - while (this.subscriptions.length > 0) { - this.subscriptions.pop().unsubscribe() - } - } - - /** - * @param {boolean} - * @description when new viewer is init, if this viewer will persist - * @default false - * @TODO does it make sense to tie widget persistency with WidgetUnit class? - */ - public persistency: boolean = false - - public undock(event?: Event) { - if (event) { - event.stopPropagation() - event.preventDefault() - } - - this.widgetServices.changeState(this, { - title : this.title, - state: 'floating', - exitable: this.exitable, - persistency: this.persistency, - }) - } - - public dock(event?: Event) { - if (event) { - event.stopPropagation() - event.preventDefault() - } - - this.widgetServices.changeState(this, { - title : this.title, - state: 'docked', - exitable: this.exitable, - persistency: this.persistency, - }) - } - - public exit(event?: Event) { - if (event) { - event.stopPropagation() - event.preventDefault() - } - - this.widgetServices.exitWidget(this) - } - - public setWidthHeight() { - this.width = this.state === 'docked' ? null : '0px' - this.height = this.state === 'docked' ? null : '0px' - } - - /* floating widget specific functionalities */ - - @HostBinding('style.transform') - get styleTransform() { - return this.state === 'floating' ? `translate(${this.position.map(v => v + 'px').join(',')})` : null - } - - public position: [number, number] = [400, 100] -} diff --git a/src/widget/widgetUnit/widgetUnit.style.css b/src/widget/widgetUnit/widgetUnit.style.css deleted file mode 100644 index 9002aa7d4dcb155fb9e4fbd98ca065576625e20b..0000000000000000000000000000000000000000 --- a/src/widget/widgetUnit/widgetUnit.style.css +++ /dev/null @@ -1,91 +0,0 @@ -:host -{ - pointer-events: all; - display:block; -} - -div[widgetUnitHeading] -{ - font-size:110%; - padding : 0.5em 0.7em; - display:flex; - white-space: nowrap; -} - - div[widgetUnitHeading] > div[title] - { - flex : 1 1 0px; - overflow:hidden; - text-overflow: ellipsis; - } - - div[widgetUnitHeading] > div[icons] - { - flex : 0 0 0px; - display:flex; - } - - div[widgetUnitHeading] > div[icons] > i - { - margin-left:0.5em; - } - -.widget-body -{ - min-width:280px; -} - -:host-context([state='floating']) div[widgetUnitHeading]:hover -{ - cursor : move; -} - -@keyframes blinkDark -{ - 0% { - border-color: rgba(128, 128, 200, 0.0); - } - - 100% { - border-color: rgba(128, 128, 200, 1.0); - } -} - -@keyframes blink -{ - 0% { - border-color: rgba(128, 128, 255, 0.0); - } - - 100% { - border-color: rgba(128, 128, 255, 1.0); - } -} - -:host-context([darktheme="true"]) .blinkOn -{ - animation: 0.5s blinkDark ease-in-out 9 alternate; - border: 1px solid rgba(128, 128, 200, 1.0) !important; -} - -:host-context([darktheme="false"]) .blinkOn -{ - animation: 0.5s blink ease-in-out 9 alternate; - border: 1px solid rgba(128, 128, 255, 1.0) !important; -} - -[heading] -{ - position:relative; -} - -[heading] > [progressBar] -{ - position: absolute; - width: 100%; - height: 100%; - left: 0; - top: 0; - opacity: 0.4; - pointer-events: none; -} diff --git a/src/widget/widgetUnit/widgetUnit.template.html b/src/widget/widgetUnit/widgetUnit.template.html deleted file mode 100644 index 6af4e0338400bd94e1e36f6daad5708aa1a0c11c..0000000000000000000000000000000000000000 --- a/src/widget/widgetUnit/widgetUnit.template.html +++ /dev/null @@ -1,42 +0,0 @@ -<mat-card cdkDrag - [cdkDragDisabled]="state === 'docked'" - [ngClass]="{'blinkOn': blinkOn, 'bodyCollapsable': state === 'docked'}" - class="widget-body"> - - <!-- body --> - <mat-card-content> - <!-- top bar, drag handle etc --> - <div class="d-flex align-items-center"> - <!-- drag handle --> - <span class="hover-grab p-2" - cdkDragHandle> - <i class="fas fa-grip-vertical"></i> - </span> - - <div class="flex-grow-1"></div> - - <!-- close btn --> - <button mat-icon-button - (click)="exit($event)"> - <i class="fas fa-times"></i> - </button> - </div> - - <h4 class="mat-h4"> - <ng-template [ngTemplateOutlet]="titleTmpl"> - </ng-template> - </h4> - <ng-template #container> - </ng-template> - </mat-card-content> -</mat-card> - - -<!-- title tmpl --> -<ng-template #titleTmpl> - <div *ngIf="!titleHTML"> - {{ title }} - </div> - <div [innerHTML]="titleHTML" *ngIf="titleHTML"> - </div> -</ng-template> diff --git a/src/zipFilesOutput/downloadSingleFile.directive.ts b/src/zipFilesOutput/downloadSingleFile.directive.ts index 1ad93e0fac8a36c607965305291538cacb3108e7..d23f0dad79766203bba582003d39208a609f5505 100644 --- a/src/zipFilesOutput/downloadSingleFile.directive.ts +++ b/src/zipFilesOutput/downloadSingleFile.directive.ts @@ -12,12 +12,18 @@ export class SingleFileOutput { @Input('single-file-output') singleFile: TZipFileConfig + @Input('single-file-output-filename') + singleFileFileName: string + + @Input('single-file-output-blob') + singleFileBlob: Blob + @HostListener('click') - onClick(){ + onClick(): void{ const anchor = this.doc.createElement('a') - const blob = new Blob([this.singleFile.filecontent], { type: 'text/plain' }) + const blob = this.singleFileBlob || new Blob([this.singleFile.filecontent], { type: 'text/plain' }) anchor.href = URL.createObjectURL(blob) - anchor.download = this.singleFile.filename + anchor.download = this.singleFileFileName || this.singleFile.filename this.doc.body.appendChild(anchor) anchor.click() diff --git a/src/zipFilesOutput/zipFilesOutput.directive.ts b/src/zipFilesOutput/zipFilesOutput.directive.ts index dc980728cfd8675ecfaaf57eb1c4317f74556ba0..9b154497a29255abd44a175ca87d39449027be96 100644 --- a/src/zipFilesOutput/zipFilesOutput.directive.ts +++ b/src/zipFilesOutput/zipFilesOutput.directive.ts @@ -36,7 +36,7 @@ export class ZipFilesOutput { } @HostListener('click') - async onClick(){ + async onClick(): Promise<void>{ if (Array.isArray(this.zipFiles)) { await this.zipArray(this.zipFiles) return diff --git a/third_party/vanilla_nehuba.js b/third_party/vanilla_nehuba.js index d28509fb281124c7a82f575c77395cc38f031aa2..7bd0a86dd06b66afae50c14f69ca3c74d3c1ad22 100644 --- a/third_party/vanilla_nehuba.js +++ b/third_party/vanilla_nehuba.js @@ -1,3 +1,7 @@ (() => { - export_nehuba.createNehubaViewer({}, err => console.error(err)) + if (!export_nehuba) { + console.warn(`export_nehuba is not defined. Did you forget to import vanilla nehuba?`) + return + } + window.nehubaViewer = export_nehuba.createNehubaViewer({}, err => console.error(err)) })() \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index 9eecf098ab9df8a94086e9c511f3800652f701c8..95493e7241e42229321c881be819aa585c90d02f 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -3,9 +3,10 @@ "exclude": [ "**/*.spec.ts", "./spec/*", - "src/atlasViewerExports/*" + "src/atlasViewerExports/*", + "**/*.stories.*" ], "files": [ "src/main-aot.ts" ] -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 99482118bf1420f2acbd875d83eba79992a3b08c..7e2716b1f639eb80fe50f7ac5f3842c1a0ccbe18 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,13 @@ "third_party/*" : ["third_party/*"], "src/*" : ["src/*"], "common/*": ["common/*"] - } + }, + /** + * related: https://github.com/storybookjs/storybook/issues/9241 + * related: https://stackoverflow.com/questions/63457043/error-ts2314-generic-type-modulewithproviderst-requires-1-type-arguments + * if shedding storybookjs dev dependency, maybe shed this as well + */ + "skipLibCheck": true }, "angularCompilerOptions":{ "fullTemplateTypeCheck": true, diff --git a/tslint.json b/tslint.json deleted file mode 100644 index d96430c1d0da68127028648f02108ce49b766912..0000000000000000000000000000000000000000 --- a/tslint.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "defaultSeverity": "error", - "extends": [ - "tslint:recommended" - ], - "jsRules": {}, - "rules": { - "variable-name": { - "options": [ - "allow-leading-underscore" - ] - }, - "indent": [true, "spaces", 2], - "quotemark":false, - "semicolon":false, - "object-literal-sort-keys":false, - "arrow-parens": false, - "no-var-requires": false, - "ban-types": false, - "no-require-imports": { - "severity":"off" - }, - "interface-name": { - "severity": "off" - }, - "member-ordering": { - "options": [ - { - "order": "statics-first", - "alphabetize": true - } - ], - "severity": "off" - }, - "max-line-length": false - }, - "rulesDirectory": [] -} \ No newline at end of file diff --git a/worker/worker-typedarray.js b/worker/worker-typedarray.js index e501bfa19de2db4892f542131403e2db1a63ab4d..6157de059e71cb5f1e78c44199f02f8cc3c1dba3 100644 --- a/worker/worker-typedarray.js +++ b/worker/worker-typedarray.js @@ -1,4 +1,124 @@ (function(exports){ + /** + * CM_CONST adopted from https://github.com/bpostlethwaite/colormap + * at commit hash 3406182 + * + * with MIT license + * + * Copyright (c) <2012> ICRL + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://github.com/bpostlethwaite/colormap/blob/3406182/colorScale.js + */ + const CM_CONST = { + "jet":[{"index":0,"rgb":[0,0,131]},{"index":0.125,"rgb":[0,60,170]},{"index":0.375,"rgb":[5,255,255]},{"index":0.625,"rgb":[255,255,0]},{"index":0.875,"rgb":[250,0,0]},{"index":1,"rgb":[128,0,0]}], + "viridis": [{"index":0,"rgb":[68,1,84]},{"index":0.13,"rgb":[71,44,122]},{"index":0.25,"rgb":[59,81,139]},{"index":0.38,"rgb":[44,113,142]},{"index":0.5,"rgb":[33,144,141]},{"index":0.63,"rgb":[39,173,129]},{"index":0.75,"rgb":[92,200,99]},{"index":0.88,"rgb":[170,220,50]},{"index":1,"rgb":[253,231,37]}], + "plasma": [{"index":0,"rgb":[13,8,135]},{"index":0.13,"rgb":[75,3,161]},{"index":0.25,"rgb":[125,3,168]},{"index":0.38,"rgb":[168,34,150]},{"index":0.5,"rgb":[203,70,121]},{"index":0.63,"rgb":[229,107,93]},{"index":0.75,"rgb":[248,148,65]},{"index":0.88,"rgb":[253,195,40]},{"index":1,"rgb":[240,249,33]}], + "magma": [{"index":0,"rgb":[0,0,4]},{"index":0.13,"rgb":[28,16,68]},{"index":0.25,"rgb":[79,18,123]},{"index":0.38,"rgb":[129,37,129]},{"index":0.5,"rgb":[181,54,122]},{"index":0.63,"rgb":[229,80,100]},{"index":0.75,"rgb":[251,135,97]},{"index":0.88,"rgb":[254,194,135]},{"index":1,"rgb":[252,253,191]}], + "inferno": [{"index":0,"rgb":[0,0,4]},{"index":0.13,"rgb":[31,12,72]},{"index":0.25,"rgb":[85,15,109]},{"index":0.38,"rgb":[136,34,106]},{"index":0.5,"rgb":[186,54,85]},{"index":0.63,"rgb":[227,89,51]},{"index":0.75,"rgb":[249,140,10]},{"index":0.88,"rgb":[249,201,50]},{"index":1,"rgb":[252,255,164]}], + "greyscale": [{"index":0,"rgb":[0,0,0]},{"index":1,"rgb":[255,255,255]}], + } + + function lerp(min, max, val) { + const absDiff = max - min + const lowerVal = (val - min) / absDiff + return [lowerVal, 1 - lowerVal] + } + + function getLerpToCm(colormap) { + if (!CM_CONST[colormap]) { + throw new Error(`colormap ${colormap} does not exist in CM_CONST`) + } + const cm = CM_CONST[colormap] + + function check(nv, current) { + const lower = cm[current].index <= nv + const higher = nv <= cm[current + 1].index + let returnVal = null + if (lower && higher) { + returnVal = { + index: nv, + rgb: [0, 0, 0] + } + const [ lowerPc, higherPc ] = lerp(cm[current].index, cm[current + 1].index, nv) + returnVal.rgb = returnVal.rgb.map((_, idx) => cm[current]['rgb'][idx] * lowerPc + cm[current + 1]['rgb'][idx] * higherPc) + } + return [ returnVal, { lower, higher } ] + } + return function lerpToCm(nv) { + let minLow = 0, maxHigh = cm.length, current = Math.floor((maxHigh + minLow) / 2), found + let iter = 0 + while (true) { + iter ++ + if (iter > 100) { + throw new Error(`iter > 1000, something we`) + } + const [val, { lower, higher }] = check(nv, current) + if (val) { + found = val + break + } + if (lower) { + minLow = current + } + if (higher) { + maxHigh = current + } + current = Math.floor((maxHigh + minLow) / 2) + } + return found + } + } + + + function unpackToArray(inputArray, width, height, channel, dtype) { + /** + * NB: assuming C (row major) order! + */ + + if (channel !== 1) { + throw new Error(`cm2rgba channel must be 1, but is ${channel} instead`) + } + const depth = (() => { + if (dtype === "int32") return 4 + if (dtype === "float32") return 4 + if (dtype === "uint8") return 1 + throw new Error(`unrecognised dtype: ${dtype}`) + })() + if (width * height * depth !== inputArray.length) { + throw new Error(`expecting width * height * depth === input.length, but ${width} * ${height} * ${depth} === ${width * height * depth} !== ${inputArray.length}`) + } + + const UseConstructor = (() => { + + if (dtype === "int32") return Int32Array + if (dtype === "float32") return Float32Array + if (dtype === "uint8") return Uint8Array + throw new Error(`unrecognised dtype: ${dtype}`) + })() + + const newArray = new UseConstructor(inputArray.buffer) + const outputArray = [] + let min = null + let max = null + for (let y = 0; y < height; y ++) { + if (!outputArray[y]) outputArray[y] = [] + for (let x = 0; x < width; x++) { + const num = newArray[y * width + x] + min = min === null ? num : Math.min(num, min) + max = max === null ? num : Math.max(num, max) + outputArray[y][x] = newArray[y * width + x] + } + } + return { + outputArray, + min, + max + } + } + exports.typedArray = { fortranToRGBA(inputArray, width, height, channel) { if (channel !== 1 && channel !== 3) { @@ -28,6 +148,52 @@ } } return buffer + }, + cm2rgba(inputArray, width, height, channel, dtype, processParams) { + const { + outputArray, + min, + max, + } = unpackToArray(inputArray, width, height, channel, dtype) + const { + colormap="jet" + } = processParams || {} + + const _ = new ArrayBuffer(width * height * 4) + const buffer = new Uint8ClampedArray(_) + + const absDiff = max - min + const lerpToCm = getLerpToCm(colormap) + + for (let row = 0; row < height; row ++) { + for (let col = 0; col < width; col ++){ + const normalizedValue = (outputArray[row][col] - min) / absDiff + const { rgb } = lerpToCm(normalizedValue) + + const toIdx = (row * width + col) * 4 + buffer[toIdx] = rgb[0] + buffer[toIdx + 1] = rgb[1] + buffer[toIdx + 2] = rgb[2] + buffer[toIdx + 3] = 255 + } + } + return { + buffer, + min, + max, + } + }, + rawArray(inputArray, width, height, channel, dtype) { + const { + outputArray, + min, + max, + } = unpackToArray(inputArray, width, height, channel, dtype) + return { + outputArray, + min, + max, + } } } })( diff --git a/worker/worker.js b/worker/worker.js index 04a3db58bd8cbac500f2ab0dedc896ea2de7f979..ab8cc83715853cc94fb09e4a2bfce4b5f2597b6a 100644 --- a/worker/worker.js +++ b/worker/worker.js @@ -17,7 +17,6 @@ if (typeof self.importScripts === 'function') self.importScripts('./worker-type */ const validTypes = [ - 'GET_LANDMARKS_VTK', 'GET_USERLANDMARKS_VTK', 'PROPAGATE_PARC_REGION_ATTR' ] @@ -26,16 +25,21 @@ const VALID_METHOD = { PROCESS_PLOTLY: `PROCESS_PLOTLY`, PROCESS_NIFTI: 'PROCESS_NIFTI', PROCESS_TYPED_ARRAY: `PROCESS_TYPED_ARRAY`, + PROCESS_TYPED_ARRAY_F2RGBA: `PROCESS_TYPED_ARRAY_F2RGBA`, + PROCESS_TYPED_ARRAY_CM2RGBA: "PROCESS_TYPED_ARRAY_CM2RGBA", + PROCESS_TYPED_ARRAY_RAW: "PROCESS_TYPED_ARRAY_RAW", } const VALID_METHODS = [ VALID_METHOD.PROCESS_PLOTLY, VALID_METHOD.PROCESS_NIFTI, VALID_METHOD.PROCESS_TYPED_ARRAY, + VALID_METHOD.PROCESS_TYPED_ARRAY_F2RGBA, + VALID_METHOD.PROCESS_TYPED_ARRAY_CM2RGBA, + VALID_METHOD.PROCESS_TYPED_ARRAY_RAW, ] const validOutType = [ - 'ASSEMBLED_LANDMARKS_VTK', 'ASSEMBLED_USERLANDMARKS_VTK', ] @@ -170,32 +174,6 @@ const parseLmToVtk = (landmarks, scale) => { .concat(reduce.labelString.join('\n')) } -let landmarkVtkUrl - -const getLandmarksVtk = (action) => { - - // landmarks are array of triples in nm (array of array of numbers) - const landmarks = action.landmarks - const template = action.template - const scale = action.scale - ? action.scale - : 2.8 - - const vtk = parseLmToVtk(landmarks, scale) - - if(!vtk) return - - // when new set of landmarks are to be displayed, the old landmarks will be discarded - if(landmarkVtkUrl) URL.revokeObjectURL(landmarkVtkUrl) - - landmarkVtkUrl = URL.createObjectURL(new Blob( [encoder.encode(vtk)], {type : 'application/octet-stream'} )) - postMessage({ - type : 'ASSEMBLED_LANDMARKS_VTK', - template, - url : landmarkVtkUrl - }) -} - let userLandmarkVtkUrl const getuserLandmarksVtk = (action) => { @@ -295,6 +273,27 @@ onmessage = (message) => { } } if (message.data.method === VALID_METHOD.PROCESS_TYPED_ARRAY) { + try { + const { inputArray, dtype, width, height, channel } = message.data.param + const array = self.typedArray.packNpArray(inputArray, dtype, width, height, channel) + + postMessage({ + id, + result: { + array + } + }) + } catch (e) { + postMessage({ + id, + error: { + code: 401, + message: `process typed array error: ${e.toString()}` + } + }) + } + } + if (message.data.method === VALID_METHOD.PROCESS_TYPED_ARRAY_F2RGBA) { try { const { inputArray, width, height, channel } = message.data.param const buffer = self.typedArray.fortranToRGBA(inputArray, width, height, channel) @@ -315,11 +314,57 @@ onmessage = (message) => { }) } } + if (message.data.method === VALID_METHOD.PROCESS_TYPED_ARRAY_CM2RGBA) { + try { + const { inputArray, width, height, channel, dtype, processParams } = message.data.param + const { buffer, min, max } = self.typedArray.cm2rgba(inputArray, width, height, channel, dtype, processParams) + + postMessage({ + id, + result: { + buffer, + min, + max, + } + }, [ buffer.buffer ]) + } catch (e) { + postMessage({ + id, + error: { + code: 401, + message: `process typed array error: ${e.toString()}` + } + }) + } + } + if (message.data.method === VALID_METHOD.PROCESS_TYPED_ARRAY_RAW) { + try { + const { inputArray, width, height, channel, dtype, processParams } = message.data.param + const { outputArray, min, max } = self.typedArray.rawArray(inputArray, width, height, channel, dtype, processParams) + + postMessage({ + id, + result: { + outputArray, + min, + max, + } + }) + } catch (e) { + postMessage({ + id, + error: { + code: 401, + message: `process typed array error: ${e.toString()}` + } + }) + } + } postMessage({ id, error: { code: 404, - message: `worker method not found` + message: `worker method not found. ${message.data.method}` } }) return @@ -327,9 +372,6 @@ onmessage = (message) => { if(validTypes.findIndex(type => type === message.data.type) >= 0){ switch(message.data.type){ - case 'GET_LANDMARKS_VTK': - getLandmarksVtk(message.data) - return case 'GET_USERLANDMARKS_VTK': getuserLandmarksVtk(message.data) return