diff --git a/.github/workflows/create-gh-release.yml b/.github/workflows/create-gh-release.yml index 96fbbcaa..64bc7bbe 100644 --- a/.github/workflows/create-gh-release.yml +++ b/.github/workflows/create-gh-release.yml @@ -36,6 +36,14 @@ jobs: working-directory: ./Frontend/library run: npm run build + - name: Install ui-library deps + working-directory: ./Frontend/ui-library + run: npm ci + + - name: Build frontend ui-library + working-directory: ./Frontend/ui-library + run: npm run build-all + - name: Install implementations/EpicGames deps working-directory: ./Frontend/implementations/EpicGames run: npm ci @@ -54,7 +62,7 @@ jobs: path: 'PixelStreamingInfrastructure-${{ github.ref_name }}-${{ steps.getversion.outputs.version }}' type: 'tar' filename: '${{ github.ref_name }}-${{ steps.getversion.outputs.version }}.tar.gz' - exclusions: '.git .github output Frontend/Docs Frontend/library/dist Frontend/library/types Frontend/library/node_modules Frontend/implementations/EpicGames/node_modules' + exclusions: '.git .github output Frontend/Docs Frontend/library/dist Frontend/library/types Frontend/library/node_modules Frontend/ui-library/dist Frontend/ui-library/types Frontend/ui-library/node_modules Frontend/implementations/EpicGames/node_modules' - name: Archive Release .zip uses: thedoctor0/zip-release@0.7.1 diff --git a/.github/workflows/publish-ui-library-to-npm.yml b/.github/workflows/publish-ui-library-to-npm.yml new file mode 100644 index 00000000..a8e602f9 --- /dev/null +++ b/.github/workflows/publish-ui-library-to-npm.yml @@ -0,0 +1,22 @@ +name: Publish ui-library package to npmjs +on: + push: + branches: ['UE5.2'] + paths: ['Frontend/ui-library/package.json'] +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: Frontend/ui-library + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '16.x' + registry-url: 'https://registry.npmjs.org' + - run: npm ci + - run: npm run build + - run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/Frontend/README.md b/Frontend/README.md index 67f1c401..c7dd11c5 100644 --- a/Frontend/README.md +++ b/Frontend/README.md @@ -28,6 +28,11 @@ Once you have NodeJS installed, - `npm install` - `npm run build` +The default user interface is provided in /ui-library directory. You can either use it or provide your own user interface. To build the default UI, run +- `cd ui-library` +- `npm install` +- `npm run build` + If you are developing your implementation based on the library, the process is similar: diff --git a/Frontend/implementations/EpicGames/package-lock.json b/Frontend/implementations/EpicGames/package-lock.json index e8f099ec..c25f0df6 100644 --- a/Frontend/implementations/EpicGames/package-lock.json +++ b/Frontend/implementations/EpicGames/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@epicgames-ps/reference-pixelstreamingfrontend", + "name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.2", "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "@epicgames-ps/reference-pixelstreamingfrontend", + "name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.2", "version": "0.0.1", "devDependencies": { "css-loader": "^6.7.3", @@ -20,14 +20,37 @@ } }, "../../library": { - "name": "@epicgames/lib-pixelstreamingfrontend", + "name": "@epicgames-ps/lib-pixelstreamingfrontend-ue5.2", + "version": "0.0.2", + "extraneous": true, + "license": "MIT", + "dependencies": { + "sdp": "^3.1.0" + }, + "devDependencies": { + "@types/webxr": "^0.5.1", + "@typescript-eslint/eslint-plugin": "^5.16.0", + "@typescript-eslint/parser": "^5.16.0", + "cspell": "^4.1.0", + "eslint": "^8.11.0", + "prettier": "2.8.3", + "ts-loader": "^9.4.2", + "typedoc": "^0.23.24", + "typescript": "^4.9.4", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1" + } + }, + "../../ui-library": { + "name": "@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.2", "version": "0.0.1", "extraneous": true, + "license": "MIT", "dependencies": { + "@epicgames-ps/lib-pixelstreamingfrontend-ue5.2": "0.0.1", "jss": "^10.9.2", "jss-plugin-camel-case": "^10.9.2", - "jss-plugin-global": "^10.9.2", - "sdp": "^3.1.0" + "jss-plugin-global": "^10.9.2" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.16.0", diff --git a/Frontend/implementations/EpicGames/package.json b/Frontend/implementations/EpicGames/package.json index c8b2fa1f..c0be8e20 100644 --- a/Frontend/implementations/EpicGames/package.json +++ b/Frontend/implementations/EpicGames/package.json @@ -9,8 +9,8 @@ "watch": "npx webpack --watch", "serve": "webpack serve --config webpack.dev.js", "serve-prod": "webpack serve --config webpack.prod.js", - "build-all": "cd ../../library && npm run build && cd ../implementations/EpicGames && npm link ../../library && npm run build", - "build-all-dev": "cd ../../library && npm run build-dev && cd ../implementations/EpicGames && npm link ../../library && npm run build-dev" + "build-all": "cd ../../library && npm run build && cd ../ui-library && npm run build-all && cd ../implementations/EpicGames && npm link ../../library ../../ui-library && npm run build", + "build-dev-all": "cd ../../library && npm run build-dev && cd ../ui-library && npm run build-dev-all && cd ../implementations/EpicGames && npm link ../../library ../../ui-library && npm run build-dev" }, "devDependencies": { "webpack-cli": "^5.0.1", diff --git a/Frontend/implementations/EpicGames/src/player.ts b/Frontend/implementations/EpicGames/src/player.ts index 31e57a87..c229b809 100644 --- a/Frontend/implementations/EpicGames/src/player.ts +++ b/Frontend/implementations/EpicGames/src/player.ts @@ -1,17 +1,21 @@ // Copyright Epic Games, Inc. All Rights Reserved. -import * as libfrontend from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { Config, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { Application, PixelStreamingApplicationStyle } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.2'; +export const PixelStreamingApplicationStyles = + new PixelStreamingApplicationStyle(); document.body.onload = function() { // Example of how to set the logger level - //libfrontend.Logger.SetLoggerVerbosity(10); + // Logger.SetLoggerVerbosity(10); // Create a config object - let config = new libfrontend.Config(); + const config = new Config({ useUrlParams: true }); // Create a Native DOM delegate instance that implements the Delegate interface class - let application = new libfrontend.Application(config); + const pixelStreaming = new PixelStreaming(config); + const application = new Application({ pixelStreaming }); // document.getElementById("centrebox").appendChild(application.rootElement); document.body.appendChild(application.rootElement); } diff --git a/Frontend/implementations/EpicGames/src/stresstest.ts b/Frontend/implementations/EpicGames/src/stresstest.ts index cef77b57..2e96c7db 100644 --- a/Frontend/implementations/EpicGames/src/stresstest.ts +++ b/Frontend/implementations/EpicGames/src/stresstest.ts @@ -1,6 +1,9 @@ // Copyright Epic Games, Inc. All Rights Reserved. -import * as libfrontend from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { Config, Flags, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { Application, PixelStreamingApplicationStyle } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.2'; +export const PixelStreamingApplicationStyles = + new PixelStreamingApplicationStyle(); // This is the entrypoint to the stress test, all setup happens here export class StressTester { @@ -34,8 +37,8 @@ export class StressTester { this.setupPlayPause(); document.getElementById("creationIntervalInput").onchange = (event : Event) => { - let inputElem = document.getElementById("creationIntervalInput") as HTMLInputElement; - let parsedValue = Number.parseInt(inputElem.value); + const inputElem = document.getElementById("creationIntervalInput") as HTMLInputElement; + const parsedValue = Number.parseInt(inputElem.value); if(!Number.isNaN(parsedValue)) { this.streamCreationIntervalMs = parsedValue * 1000.0; this.startStreamCreation(); @@ -43,18 +46,18 @@ export class StressTester { } document.getElementById("deletionIntervalInput").onchange = (event: Event) => { - let inputElem = document.getElementById("deletionIntervalInput") as HTMLInputElement; - let parsedValue = Number.parseInt(inputElem.value); + const inputElem = document.getElementById("deletionIntervalInput") as HTMLInputElement; + const parsedValue = Number.parseInt(inputElem.value); if (!Number.isNaN(parsedValue)) { this.streamDeletionIntervalMs = parsedValue * 1000.0; this.startStreamDeletion(); } } - let creationIntervalInput = document.getElementById("creationIntervalInput") as HTMLInputElement; + const creationIntervalInput = document.getElementById("creationIntervalInput") as HTMLInputElement; creationIntervalInput.value = (this.streamCreationIntervalMs / 1000.0).toString(); - let deletionIntervalInput = document.getElementById("deletionIntervalInput") as HTMLInputElement; + const deletionIntervalInput = document.getElementById("deletionIntervalInput") as HTMLInputElement; deletionIntervalInput.value = (this.streamDeletionIntervalMs / 1000.0).toString(); } @@ -85,15 +88,15 @@ export class StressTester { this.creationIntervalHandle = setInterval(() => { if(this.play) { - let curNPeers = this.pixelStreamingFrames.length; + const curNPeers = this.pixelStreamingFrames.length; if(curNPeers >= this.maxPeers) return; - let maxPeersToCreate = this.maxPeers - curNPeers; - let nPeersToCreate = Math.ceil(Math.random() * maxPeersToCreate); + const maxPeersToCreate = this.maxPeers - curNPeers; + const nPeersToCreate = Math.ceil(Math.random() * maxPeersToCreate); for(let i = 0; i < nPeersToCreate; i++) { - let frame = this.createPixelStreamingFrame(); - let n = this.pixelStreamingFrames.length; + const frame = this.createPixelStreamingFrame(); + const n = this.pixelStreamingFrames.length; frame.id = `PixelStreamingFrame_${n + 1}`; this.streamsContainer.append(frame); this.pixelStreamingFrames.push(frame); @@ -112,19 +115,19 @@ export class StressTester { this.deletionIntervalHandle = setInterval(() => { if(!this.play) return; - let curNPeers = this.pixelStreamingFrames.length; + const curNPeers = this.pixelStreamingFrames.length; if(curNPeers === 0) return; - let nPeersToDelete = Math.ceil(Math.random() * curNPeers); + const nPeersToDelete = Math.ceil(Math.random() * curNPeers); for(let i = 0; i < nPeersToDelete; i++) { - let frame = this.pixelStreamingFrames.shift(); + const frame = this.pixelStreamingFrames.shift(); frame.parentNode.removeChild(frame); } }, this.streamDeletionIntervalMs); } private setupPlayPause() : void { - let playPauseBtn = document.getElementById("playPause"); + const playPauseBtn = document.getElementById("playPause"); playPauseBtn.innerHTML = this.play ? "Pause" : "Play"; playPauseBtn.onclick = (event : Event) => { @@ -134,24 +137,25 @@ export class StressTester { } private createPixelStreamingFrame() : HTMLElement { - let streamFrame = document.createElement("div"); + const streamFrame = document.createElement("div"); - let config = new libfrontend.Config(); - config.setFlagEnabled(libfrontend.Flags.AutoConnect, true); - config.setFlagEnabled(libfrontend.Flags.AutoPlayVideo, true); - config.setFlagEnabled(libfrontend.Flags.StartVideoMuted, true); + const config = new Config(); + config.setFlagEnabled(Flags.AutoConnect, true); + config.setFlagEnabled(Flags.AutoPlayVideo, true); + config.setFlagEnabled(Flags.StartVideoMuted, true); // Create a Native DOM delegate instance that implements the Delegate interface class - let application = new libfrontend.Application(config); + const pixelStreaming = new PixelStreaming(config); + const application = new Application({ pixelStreaming }); streamFrame.appendChild(application.rootElement); return streamFrame; } private updateTotalStreams() : void { - let nStreamsLabel = document.getElementById("nStreamsLabel"); + const nStreamsLabel = document.getElementById("nStreamsLabel"); nStreamsLabel.innerHTML = this.totalStreams.toString(); } } -let tester = new StressTester(); +const tester = new StressTester(); tester.startStressTest(); \ No newline at end of file diff --git a/Frontend/implementations/EpicGames/src/uiless.html b/Frontend/implementations/EpicGames/src/uiless.html new file mode 100644 index 00000000..d7b36b34 --- /dev/null +++ b/Frontend/implementations/EpicGames/src/uiless.html @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + Pixel Streaming + + + + +
+
+
Click to play
+
+ + + \ No newline at end of file diff --git a/Frontend/implementations/EpicGames/src/uiless.ts b/Frontend/implementations/EpicGames/src/uiless.ts new file mode 100644 index 00000000..f0998a88 --- /dev/null +++ b/Frontend/implementations/EpicGames/src/uiless.ts @@ -0,0 +1,32 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import { Config, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; + +document.body.onload = function() { + // Example of how to set the logger level + // Logger.SetLoggerVerbosity(10); + + // Create a config object + const config = new Config({ + initialSettings: { + AutoPlayVideo: true, + AutoConnect: true, + ss: "ws://localhost:80", + StartVideoMuted: true, + } + }); + + // Create a PixelStreaming instance and attach the video element to an existing parent div + const pixelStreaming = new PixelStreaming(config, { videoElementParent: document.getElementById("videoParentElement")}); + + // If browser denies autoplay, show "Click to play" and register a click-to-play handler + pixelStreaming.addEventListener("playStreamRejected", () => { + const clickToPlay = document.getElementById("clickToPlayElement"); + clickToPlay.className = "visible"; + clickToPlay.onclick = () => { + pixelStreaming.play(); + clickToPlay.className = ""; + clickToPlay.onclick = undefined; + } + }) +} diff --git a/Frontend/library/package-lock.json b/Frontend/library/package-lock.json index 98e85861..ba820586 100644 --- a/Frontend/library/package-lock.json +++ b/Frontend/library/package-lock.json @@ -9,13 +9,10 @@ "version": "0.0.2", "license": "MIT", "dependencies": { - "@types/webxr": "^0.5.1", - "jss": "^10.9.2", - "jss-plugin-camel-case": "^10.9.2", - "jss-plugin-global": "^10.9.2", "sdp": "^3.1.0" }, "devDependencies": { + "@types/webxr": "^0.5.1", "@typescript-eslint/eslint-plugin": "^5.16.0", "@typescript-eslint/parser": "^5.16.0", "cspell": "^4.1.0", @@ -28,17 +25,6 @@ "webpack-cli": "^5.0.1" } }, - "node_modules/@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@cspell/dict-aws": { "version": "1.0.14", "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-1.0.14.tgz", @@ -442,7 +428,8 @@ "node_modules/@types/webxr": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.1.tgz", - "integrity": "sha512-xlFXPfgJR5vIuDefhaHuUM9uUgvPaXB6GKdXy2gdEh8gBWQZ2ul24AJz3foUd8NNKlSTQuWYJpCb1/pL81m1KQ==" + "integrity": "sha512-xlFXPfgJR5vIuDefhaHuUM9uUgvPaXB6GKdXy2gdEh8gBWQZ2ul24AJz3foUd8NNKlSTQuWYJpCb1/pL81m1KQ==", + "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.48.0", @@ -1309,11 +1296,6 @@ "node": ">= 10" } }, - "node_modules/csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1955,11 +1937,6 @@ "node": ">=8" } }, - "node_modules/hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -2089,11 +2066,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2250,40 +2222,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jss": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.2.tgz", - "integrity": "sha512-b8G6rWpYLR4teTUbGd4I4EsnWjg7MN0Q5bSsjKhVkJVjhQDy2KzkbD2AW3TuT0RYZVmZZHKIrXDn6kjU14qkUg==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "csstype": "^3.0.2", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/jss" - } - }, - "node_modules/jss-plugin-camel-case": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.2.tgz", - "integrity": "sha512-wgBPlL3WS0WDJ1lPJcgjux/SHnDuu7opmgQKSraKs4z8dCCyYMx9IDPFKBXQ8Q5dVYij1FFV0WdxyhuOOAXuTg==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "hyphenate-style-name": "^1.0.3", - "jss": "10.9.2" - } - }, - "node_modules/jss-plugin-global": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.2.tgz", - "integrity": "sha512-GcX0aE8Ef6AtlasVrafg1DItlL/tWHoC4cGir4r3gegbWwF5ZOBYhx04gurPvWHC8F873aEGqge7C17xpwmp2g==", - "dependencies": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.2" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -2756,11 +2694,6 @@ "node": ">= 10.13.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -3155,11 +3088,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3587,14 +3515,6 @@ } }, "dependencies": { - "@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", - "requires": { - "regenerator-runtime": "^0.13.11" - } - }, "@cspell/dict-aws": { "version": "1.0.14", "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-1.0.14.tgz", @@ -3961,7 +3881,8 @@ "@types/webxr": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.1.tgz", - "integrity": "sha512-xlFXPfgJR5vIuDefhaHuUM9uUgvPaXB6GKdXy2gdEh8gBWQZ2ul24AJz3foUd8NNKlSTQuWYJpCb1/pL81m1KQ==" + "integrity": "sha512-xlFXPfgJR5vIuDefhaHuUM9uUgvPaXB6GKdXy2gdEh8gBWQZ2ul24AJz3foUd8NNKlSTQuWYJpCb1/pL81m1KQ==", + "dev": true }, "@typescript-eslint/eslint-plugin": { "version": "5.48.0", @@ -4603,11 +4524,6 @@ "integrity": "sha512-or3OGKydZs1NwweMIgnA48k8H3F5zK4e5lonjUhpEzLYQZ2nB23decdoqZ8ogFC8pFTA40tZKDsMJ0b+65gX4Q==", "dev": true }, - "csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5088,11 +5004,6 @@ "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", "dev": true }, - "hyphenate-style-name": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", - "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" - }, "iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -5186,11 +5097,6 @@ "is-extglob": "^2.1.1" } }, - "is-in-browser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5313,36 +5219,6 @@ "universalify": "^2.0.0" } }, - "jss": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.2.tgz", - "integrity": "sha512-b8G6rWpYLR4teTUbGd4I4EsnWjg7MN0Q5bSsjKhVkJVjhQDy2KzkbD2AW3TuT0RYZVmZZHKIrXDn6kjU14qkUg==", - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^3.0.2", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - }, - "jss-plugin-camel-case": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.2.tgz", - "integrity": "sha512-wgBPlL3WS0WDJ1lPJcgjux/SHnDuu7opmgQKSraKs4z8dCCyYMx9IDPFKBXQ8Q5dVYij1FFV0WdxyhuOOAXuTg==", - "requires": { - "@babel/runtime": "^7.3.1", - "hyphenate-style-name": "^1.0.3", - "jss": "10.9.2" - } - }, - "jss-plugin-global": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.2.tgz", - "integrity": "sha512-GcX0aE8Ef6AtlasVrafg1DItlL/tWHoC4cGir4r3gegbWwF5ZOBYhx04gurPvWHC8F873aEGqge7C17xpwmp2g==", - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.9.2" - } - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -5682,11 +5558,6 @@ "resolve": "^1.20.0" } }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -5942,11 +5813,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/Frontend/library/package.json b/Frontend/library/package.json index b1f2827a..7101980b 100644 --- a/Frontend/library/package.json +++ b/Frontend/library/package.json @@ -1,6 +1,6 @@ { "name": "@epicgames-ps/lib-pixelstreamingfrontend-ue5.2", - "version": "0.0.2", + "version": "0.1.0", "description": "Frontend library for Pixel Streaming", "main": "dist/lib-pixelstreamingfrontend.js", "types": "types/pixelstreamingfrontend.d.ts", @@ -14,6 +14,7 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.16.0", "@typescript-eslint/parser": "^5.16.0", + "@types/webxr": "^0.5.1", "cspell": "^4.1.0", "eslint": "^8.11.0", "prettier": "2.8.3", @@ -24,10 +25,6 @@ "webpack-cli": "^5.0.1" }, "dependencies": { - "@types/webxr": "^0.5.1", - "jss": "^10.9.2", - "jss-plugin-camel-case": "^10.9.2", - "jss-plugin-global": "^10.9.2", "sdp": "^3.1.0" }, "repository": { diff --git a/Frontend/library/src/AFK/AFKController.ts b/Frontend/library/src/AFK/AFKController.ts index 35b46b29..78da0925 100644 --- a/Frontend/library/src/AFK/AFKController.ts +++ b/Frontend/library/src/AFK/AFKController.ts @@ -2,7 +2,13 @@ import { Config, Flags, NumericParameters } from '../Config/Config'; import { Logger } from '../Logger/Logger'; -import { AFKOverlay } from './AFKOverlay'; +import { PixelStreaming } from '../PixelStreaming/PixelStreaming'; +import { + AfkTimedOutEvent, + AfkWarningActivateEvent, + AfkWarningDeactivateEvent, + AfkWarningUpdateEvent +} from '../Util/EventEmitter'; export class AFKController { // time out logic details @@ -13,13 +19,19 @@ export class AFKController { countDown = 0; countDownTimer: ReturnType = undefined; config: Config; - afkOverlay: AFKOverlay; + pixelStreaming: PixelStreaming; + onDismissAfk: () => void; onAFKTimedOutCallback: () => void; - constructor(config: Config, afkOverlay: AFKOverlay) { + constructor( + config: Config, + pixelStreaming: PixelStreaming, + onDismissAfk: () => void + ) { this.config = config; - this.afkOverlay = afkOverlay; + this.pixelStreaming = pixelStreaming; + this.onDismissAfk = onDismissAfk; this.onAFKTimedOutCallback = () => { console.log( 'AFK timed out, did you want to override this callback?' @@ -35,7 +47,9 @@ export class AFKController { if (this.active || this.countdownActive) { this.startAfkWarningTimer(); - this.hideCurrentOverlay(); + this.pixelStreaming.dispatchEvent( + new AfkWarningDeactivateEvent() + ); } } @@ -96,12 +110,19 @@ export class AFKController { this.pauseAfkWarningTimer(); // instantiate a new overlay - this.showAfkOverlay(); + this.pixelStreaming.dispatchEvent( + new AfkWarningActivateEvent({ + countDown: this.countDown, + dismissAfk: this.onDismissAfk + }) + ); // update our countDown timer and overlay contents this.countDown = this.closeTimeout; this.countdownActive = true; - this.afkOverlay.updateCountdown(this.countDown); + this.pixelStreaming.dispatchEvent( + new AfkWarningUpdateEvent({ countDown: this.countDown }) + ); // if we are in locked mouse exit pointerlock if (!this.config.isFlagEnabled(Flags.HoveringMouseMode)) { @@ -116,7 +137,9 @@ export class AFKController { this.countDown--; if (this.countDown == 0) { // The user failed to click so hide the overlay and disconnect them. - this.hideCurrentOverlay(); + this.pixelStreaming.dispatchEvent( + new AfkTimedOutEvent() + ); this.onAFKTimedOutCallback(); Logger.Log( Logger.GetStackTrace(), @@ -126,22 +149,10 @@ export class AFKController { // switch off the afk feature as stream has closed this.stopAfkWarningTimer(); } else { - this.afkOverlay.updateCountdown(this.countDown); + this.pixelStreaming.dispatchEvent( + new AfkWarningUpdateEvent({ countDown: this.countDown }) + ); } }, 1000); } - - /** - * An override method for showing the afk overlay - */ - showAfkOverlay() { - // Base Functionality: Do Nothing - } - - /** - * An override method for hiding the afk overlay - */ - hideCurrentOverlay() { - // Base Functionality: Do Nothing - } } diff --git a/Frontend/library/src/Application/Application.ts b/Frontend/library/src/Application/Application.ts deleted file mode 100644 index be915d39..00000000 --- a/Frontend/library/src/Application/Application.ts +++ /dev/null @@ -1,847 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -import { Config, OptionParameters } from '../Config/Config'; -import { StatsPanel } from '../UI/StatsPanel'; -import { LatencyTestResults } from '../DataChannel/LatencyTestResults'; -import { AggregatedStats } from '../PeerConnectionController/AggregatedStats'; -import { VideoQpIndicator } from '../UI/VideoQpIndicator'; -import { SettingsPanel } from '../UI/SettingsPanel'; -import { WebRtcPlayerController } from '../WebRtcPlayer/WebRtcPlayerController'; -import { Flags, NumericParameters } from '../Config/Config'; -import { Logger } from '../Logger/Logger'; -import { - InitialSettings, - EncoderSettings, - WebRTCSettings -} from '../DataChannel/InitialSettings'; -import { OnScreenKeyboard } from '../UI/OnScreenKeyboard'; -import { Controls } from '../UI/Controls'; -import { LabelledButton } from '../UI/LabelledButton'; -import { OverlayBase } from '../Overlay/BaseOverlay'; -import { ActionOverlay } from '../Overlay/ActionOverlay'; -import { TextOverlay } from '../Overlay/TextOverlay'; -import { ConnectOverlay } from '../Overlay/ConnectOverlay'; -import { DisconnectOverlay } from '../Overlay/DisconnectOverlay'; -import { PlayOverlay } from '../Overlay/PlayOverlay'; -import { InfoOverlay } from '../Overlay/InfoOverlay'; -import { ErrorOverlay } from '../Overlay/ErrorOverlay'; -import { MessageOnScreenKeyboard } from '../WebSockets/MessageReceive'; -import { WebXRController } from '../WebXR/WebXRController'; - -/** - * Provides common base functionality for applications that extend this application - */ -export class Application { - public webRtcController: WebRtcPlayerController; - public webXrController: WebXRController; - public config: Config; - - _rootElement: HTMLElement; - _uiFeatureElement: HTMLElement; - _videoElementParent: HTMLElement; - - showActionOrErrorOnDisconnect = true; - - settingsPanel: SettingsPanel; - statsPanel: StatsPanel; - videoQpIndicator: VideoQpIndicator; - onScreenKeyboardHelper: OnScreenKeyboard; - - // set the overlay placeholders - currentOverlay: OverlayBase; - disconnectOverlay: ActionOverlay; - connectOverlay: ActionOverlay; - playOverlay: ActionOverlay; - infoOverlay: TextOverlay; - errorOverlay: TextOverlay; - - videoStartTime: number; - inputController: boolean; - - /** - * @param config - A newly instantiated config object - * returns the base delegate object with the config inside it along with a new instance of the Overlay controller class - */ - constructor(config: Config) { - this.config = config; - - this.createOverlays(); - - // Add the video stream QP indicator - this.videoQpIndicator = new VideoQpIndicator(); - this.uiFeaturesElement.appendChild(this.videoQpIndicator.rootElement); - - // Add settings panel - this.settingsPanel = new SettingsPanel(); - this.uiFeaturesElement.appendChild(this.settingsPanel.rootElement); - this.configureSettings(); - - // Add stats panel - this.statsPanel = new StatsPanel(); - this.uiFeaturesElement.appendChild(this.statsPanel.rootElement); - - this.createButtons(); - - // setup WebRTC - this.setWebRtcPlayerController( - new WebRtcPlayerController(this.config, this) - ); - - // Onscreen keyboard - this.onScreenKeyboardHelper = new OnScreenKeyboard( - this.videoElementParent - ); - this.onScreenKeyboardHelper.unquantizeAndDenormalizeUnsigned = ( - x: number, - y: number - ) => - this.webRtcController.requestUnquantizedAndDenormalizeUnsigned( - x, - y - ); - this.activateOnScreenKeyboard = (command: MessageOnScreenKeyboard) => - this.onScreenKeyboardHelper.showOnScreenKeyboard(command); - - this.updateColors(this.config.isFlagEnabled(Flags.LightMode)); - - this.webXrController = new WebXRController(this.webRtcController); - } - - public createOverlays(): void { - // build all of the overlays - this.disconnectOverlay = new DisconnectOverlay(this.videoElementParent); - this.connectOverlay = new ConnectOverlay(this.videoElementParent); - this.playOverlay = new PlayOverlay(this.videoElementParent); - this.infoOverlay = new InfoOverlay(this.videoElementParent); - this.errorOverlay = new ErrorOverlay(this.videoElementParent); - } - - /** - * Set up button click functions and button functionality - */ - public createButtons() { - // Setup controls - const controls = new Controls(); - - // When we fullscreen we want this element to be the root - controls.fullscreenIcon.fullscreenElement = this.rootElement; - this.uiFeaturesElement.appendChild(controls.rootElement); - - // Add settings button to controls - controls.settingsIcon.rootElement.onclick = () => - this.settingsClicked(); - this.settingsPanel.settingsCloseButton.onclick = () => - this.settingsClicked(); - - // Add WebXR button to controls - controls.xrIcon.rootElement.onclick = () => - this.webXrController.xrClicked(); - - // setup the stats/info button - controls.statsIcon.rootElement.onclick = () => this.statsClicked(); - - this.statsPanel.statsCloseButton.onclick = () => this.statsClicked(); - - // Add button for toggle fps - const showFPSButton = new LabelledButton('Show FPS', 'Toggle'); - showFPSButton.addOnClickListener(() => { - this.webRtcController.sendShowFps(); - }); - - // Add button for restart stream - const restartStreamButton = new LabelledButton( - 'Restart Stream', - 'Restart' - ); - restartStreamButton.addOnClickListener(() => { - this.webRtcController.restartStreamAutomatically(); - }); - - // Add button for request keyframe - const requestKeyframeButton = new LabelledButton( - 'Request keyframe', - 'Request' - ); - requestKeyframeButton.addOnClickListener(() => { - this.webRtcController.sendIframeRequest(); - }); - - const commandsSectionElem = this.config.buildSectionWithHeading( - this.settingsPanel.settingsContentElement, - 'Commands' - ); - commandsSectionElem.appendChild(showFPSButton.rootElement); - commandsSectionElem.appendChild(requestKeyframeButton.rootElement); - commandsSectionElem.appendChild(restartStreamButton.rootElement); - } - - /** - * Gets the rootElement of the application, video stream and all UI are children of this element. - */ - public get rootElement(): HTMLElement { - if (!this._rootElement) { - this._rootElement = document.createElement('div'); - this._rootElement.id = 'playerUI'; - this._rootElement.classList.add('noselect'); - this._rootElement.appendChild(this.videoElementParent); - this._rootElement.appendChild(this.uiFeaturesElement); - } - return this._rootElement; - } - - /** - * Gets the element that contains the video stream element. - */ - public get videoElementParent(): HTMLElement { - if (!this._videoElementParent) { - this._videoElementParent = document.createElement('div'); - this._videoElementParent.id = 'videoElementParent'; - } - return this._videoElementParent; - } - - /** - * Gets the element that contains all the UI features, like the stats and settings panels. - */ - public get uiFeaturesElement(): HTMLElement { - if (!this._uiFeatureElement) { - this._uiFeatureElement = document.createElement('div'); - this._uiFeatureElement.id = 'uiFeatures'; - } - return this._uiFeatureElement; - } - - /** - * Configure the settings with on change listeners and any additional per experience settings. - */ - configureSettings(): void { - // This builds all the settings sections and flags under this `settingsContent` element. - this.config.populateSettingsElement( - this.settingsPanel.settingsContentElement - ); - - this.config.addOnSettingChangedListener( - Flags.IsQualityController, - (wantsQualityController: boolean) => { - // If the setting has been set to true (either programatically or the user has flicked the toggle) - // and we aren't currently quality controller, send the request - if ( - wantsQualityController === true && - !this.webRtcController.isQualityController - ) { - this.webRtcController.sendRequestQualityControlOwnership(); - } - } - ); - - this.config.addOnSettingChangedListener( - Flags.AFKDetection, - (isAFKEnabled: boolean) => { - this.webRtcController.setAfkEnabled(isAFKEnabled); - } - ); - - this.config.addOnSettingChangedListener( - Flags.MatchViewportResolution, - () => { - this.webRtcController.videoPlayer.updateVideoStreamSize(); - } - ); - - this.config.addOnSettingChangedListener( - Flags.HoveringMouseMode, - (isHoveringMouse: boolean) => { - this.config.setFlagLabel( - Flags.HoveringMouseMode, - `Control Scheme: ${ - isHoveringMouse ? 'Hovering' : 'Locked' - } Mouse` - ); - this.webRtcController.activateRegisterMouse(); - } - ); - - this.config.addOnSettingChangedListener( - Flags.LightMode, - (isLightMode: boolean) => { - this.config.setFlagLabel( - Flags.LightMode, - `Color Scheme: ${isLightMode ? 'Light' : 'Dark'} Mode` - ); - this.updateColors(isLightMode); - } - ); - - // encoder settings - this.config.addOnNumericSettingChangedListener( - NumericParameters.MinQP, - (newValue: number) => { - Logger.Log( - Logger.GetStackTrace(), - '-------- Sending encoder settings --------', - 7 - ); - const encode: EncoderSettings = { - MinQP: newValue, - MaxQP: this.config.getNumericSettingValue( - NumericParameters.MaxQP - ) - }; - this.webRtcController.sendEncoderSettings(encode); - Logger.Log( - Logger.GetStackTrace(), - '-------------------------------------------', - 7 - ); - } - ); - - this.config.addOnNumericSettingChangedListener( - NumericParameters.MaxQP, - (newValue: number) => { - Logger.Log( - Logger.GetStackTrace(), - '-------- Sending encoder settings --------', - 7 - ); - const encode: EncoderSettings = { - MinQP: this.config.getNumericSettingValue( - NumericParameters.MinQP - ), - MaxQP: newValue - }; - this.webRtcController.sendEncoderSettings(encode); - Logger.Log( - Logger.GetStackTrace(), - '-------------------------------------------', - 7 - ); - } - ); - - // WebRTC settings - this.config.addOnNumericSettingChangedListener( - NumericParameters.WebRTCMinBitrate, - (newValue: number) => { - Logger.Log( - Logger.GetStackTrace(), - '-------- Sending web rtc settings --------', - 7 - ); - const webRtcSettings: WebRTCSettings = { - FPS: this.config.getNumericSettingValue( - NumericParameters.WebRTCFPS - ), - MinBitrate: newValue * 1000, - MaxBitrate: - this.config.getNumericSettingValue( - NumericParameters.WebRTCMaxBitrate - ) * 1000 - }; - this.webRtcController.sendWebRtcSettings(webRtcSettings); - Logger.Log( - Logger.GetStackTrace(), - '-------------------------------------------', - 7 - ); - } - ); - - this.config.addOnNumericSettingChangedListener( - NumericParameters.WebRTCMaxBitrate, - (newValue: number) => { - Logger.Log( - Logger.GetStackTrace(), - '-------- Sending web rtc settings --------', - 7 - ); - const webRtcSettings: WebRTCSettings = { - FPS: this.config.getNumericSettingValue( - NumericParameters.WebRTCFPS - ), - MinBitrate: - this.config.getNumericSettingValue( - NumericParameters.WebRTCMinBitrate - ) * 1000, - MaxBitrate: newValue * 1000 - }; - this.webRtcController.sendWebRtcSettings(webRtcSettings); - Logger.Log( - Logger.GetStackTrace(), - '-------------------------------------------', - 7 - ); - } - ); - - this.config.addOnNumericSettingChangedListener( - NumericParameters.WebRTCFPS, - (newValue: number) => { - Logger.Log( - Logger.GetStackTrace(), - '-------- Sending web rtc settings --------', - 7 - ); - const webRtcSettings: WebRTCSettings = { - FPS: newValue, - MinBitrate: - this.config.getNumericSettingValue( - NumericParameters.WebRTCMinBitrate - ) * 1000, - MaxBitrate: - this.config.getNumericSettingValue( - NumericParameters.WebRTCMaxBitrate - ) * 1000 - }; - this.webRtcController.sendWebRtcSettings(webRtcSettings); - Logger.Log( - Logger.GetStackTrace(), - '-------------------------------------------', - 7 - ); - } - ); - - this.config.addOnOptionSettingChangedListener( - OptionParameters.PreferredCodec, - (newValue: string) => { - if (this.webRtcController) { - this.webRtcController.setPreferredCodec(newValue); - } - } - ); - } - - /** - * Shows or hides the settings panel if clicked - */ - settingsClicked() { - this.statsPanel.hide(); - this.settingsPanel.toggleVisibility(); - } - - /** - * Shows or hides the stats panel if clicked - */ - statsClicked() { - this.settingsPanel.hide(); - this.statsPanel.toggleVisibility(); - } - - /** - * Activate the on screen keyboard when receiving the command from the streamer - * @param command - the keyboard command - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - activateOnScreenKeyboard(command: MessageOnScreenKeyboard): void { - throw new Error('Method not implemented.'); - } - - /** - * Set the input control ownership - * @param inputControlOwnership - does the user have input control ownership - */ - onInputControlOwnership(inputControlOwnership: boolean): void { - this.inputController = inputControlOwnership; - } - - /** - * Shows the disconnect overlay - * @param updateText - the text that will be displayed in the overlay - */ - showDisconnectOverlay(updateText: string) { - this.hideCurrentOverlay(); - this.updateDisconnectOverlay(updateText); - this.disconnectOverlay.show(); - this.currentOverlay = this.disconnectOverlay; - } - - /** - * Update the disconnect overlays span text - * @param updateText - the new countdown number - */ - updateDisconnectOverlay(updateText: string) { - this.disconnectOverlay.update(updateText); - } - - /** - * Activates the disconnect overlays action - */ - onDisconnectionAction() { - this.disconnectOverlay.activate(); - } - - /** - * Hides the current overlay - */ - hideCurrentOverlay() { - if (this.currentOverlay != null) { - this.currentOverlay.hide(); - this.currentOverlay = null; - } - } - - /** - * Shows the connect overlay - */ - showConnectOverlay() { - this.hideCurrentOverlay(); - this.connectOverlay.show(); - this.currentOverlay = this.connectOverlay; - } - - /** - * Shows the play overlay - */ - showPlayOverlay() { - this.hideCurrentOverlay(); - this.playOverlay.show(); - this.currentOverlay = this.playOverlay; - } - - /** - * Shows the text overlay - * @param text - the text that will be shown in the overlay - */ - showTextOverlay(text: string) { - this.hideCurrentOverlay(); - this.infoOverlay.update(text); - this.infoOverlay.show(); - this.currentOverlay = this.infoOverlay; - } - - /** - * Shows the error overlay - * @param text - the text that will be shown in the overlay - */ - showErrorOverlay(text: string) { - this.hideCurrentOverlay(); - this.errorOverlay.update(text); - this.errorOverlay.show(); - this.currentOverlay = this.errorOverlay; - } - - /** - * Activates the connect overlays action - */ - onConnectAction() { - this.connectOverlay.activate(); - } - - /** - * Activates the play overlays action - */ - onPlayAction() { - this.playOverlay.activate(); - } - - /** - * Shows the afk overlay - * @param countDown - the countdown number for the afk countdown - */ - showAfkOverlay(countDown: number) { - this.hideCurrentOverlay(); - this.webRtcController.afkController.afkOverlay.updateCountdown( - countDown - ); - this.webRtcController.afkController.afkOverlay.show(); - this.currentOverlay = this.webRtcController.afkController.afkOverlay; - } - - /** - * Instantiate the WebRTCPlayerController interface to provide WebRTCPlayerController functionality within this class and set up anything that requires it - * @param webRtcPlayerController - a WebRtcPlayerController controller instance - */ - setWebRtcPlayerController(webRtcPlayerController: WebRtcPlayerController) { - this.webRtcController = webRtcPlayerController; - - this.webRtcController.setPreferredCodec( - this.config.getSettingOption(OptionParameters.PreferredCodec) - .selected - ); - this.webRtcController.resizePlayerStyle(); - - this.disconnectOverlay.onAction(() => { - this.onWebRtcAutoConnect(); - this.webRtcController.connectToSignallingServer(); - }); - - // Build the webRtc connect overlay Event Listener and show the connect overlay - this.connectOverlay.onAction(() => - this.webRtcController.connectToSignallingServer() - ); - - // set up the play overlays action - this.playOverlay.onAction(() => { - this.onStreamLoading(); - this.webRtcController.playStream(); - }); - - // set up the connect overlays action - this.showConnectOrAutoConnectOverlays(); - } - - /** - * Show the Connect Overlay or auto connect - */ - showConnectOrAutoConnectOverlays() { - // set up if the auto play will be used or regular click to start - if (!this.config.isFlagEnabled(Flags.AutoConnect)) { - this.showConnectOverlay(); - } else { - // if autoplaying show an info overlay while while waiting for the connection to begin - this.onWebRtcAutoConnect(); - this.webRtcController.connectToSignallingServer(); - } - } - - /** - * Show the webRtcAutoConnect Overlay and connect - */ - onWebRtcAutoConnect() { - this.showTextOverlay('Auto Connecting Now'); - this.showActionOrErrorOnDisconnect = true; - } - - /** - * Set up functionality to happen when receiving a webRTC answer - */ - onWebRtcSdp() { - this.showTextOverlay('WebRTC Connection Negotiated'); - } - - /** - * Shows a text overlay to alert the user the stream is currently loading - */ - onStreamLoading() { - // build the spinner span - const spinnerSpan: HTMLSpanElement = document.createElement('span'); - spinnerSpan.className = 'visually-hidden'; - spinnerSpan.innerHTML = 'Loading...'; - - // build the spinner div - const spinnerDiv: HTMLDivElement = document.createElement('div'); - spinnerDiv.id = 'loading-spinner'; - spinnerDiv.className = 'spinner-border ms-2'; - spinnerDiv.setAttribute('role', 'status'); - - // append the spinner to the element - spinnerDiv.appendChild(spinnerSpan); - - this.showTextOverlay('Loading Stream ' + spinnerDiv.outerHTML); - } - - /** - * Event fired when the video is disconnected - displays the error overlay and resets the buttons stream tools upon disconnect - * @param eventString - the event text that will be shown in the overlay - */ - onDisconnect(eventString: string) { - // if we have overridden the default disconnection message, assign the new value here - if ( - this.webRtcController.getDisconnectMessageOverride() != '' && - this.webRtcController.getDisconnectMessageOverride() !== - undefined && - this.webRtcController.getDisconnectMessageOverride() != null - ) { - eventString = this.webRtcController.getDisconnectMessageOverride(); - this.webRtcController.setDisconnectMessageOverride(''); - } - - if (this.showActionOrErrorOnDisconnect == false) { - this.showErrorOverlay(`Disconnected: ${eventString}`); - this.showActionOrErrorOnDisconnect = true; - } else { - this.showDisconnectOverlay( - `Disconnected: ${eventString}
Click To Restart
` - ); - } - - // update all of the tools upon disconnect - this.onVideoEncoderAvgQP(0); - - // disable starting a latency check - this.statsPanel.latencyTest.latencyTestButton.onclick = () => { - // do nothing - }; - } - - /** - * Handles when Web Rtc is connecting - */ - onWebRtcConnecting() { - this.showTextOverlay('Starting connection to server, please wait'); - } - - /** - * Handles when Web Rtc has connected - */ - onWebRtcConnected() { - this.showTextOverlay('WebRTC connected, waiting for video'); - } - - /** - * Handles when Web Rtc fails to connect - */ - onWebRtcFailed() { - this.showErrorOverlay('Unable to setup video'); - } - - /** - * Handle when the Video has been Initialized - */ - onVideoInitialized() { - // starting a latency check - this.statsPanel.latencyTest.latencyTestButton.onclick = () => { - this.webRtcController.sendLatencyTest(); - }; - - this.videoStartTime = Date.now(); - } - - /** - * Set up functionality to happen when receiving latency test results - * @param latency - latency test results object - */ - onLatencyTestResult(latencyTimings: LatencyTestResults) { - this.statsPanel.latencyTest.handleTestResult(latencyTimings); - } - - /** - * Set up functionality to happen when receiving video statistics - * @param videoStats - video statistics as a aggregate stats object - */ - onVideoStats(videoStats: AggregatedStats) { - // Duration - if (!this.videoStartTime || this.videoStartTime === undefined) { - this.videoStartTime = Date.now(); - } - - const deltaTime = Date.now() - this.videoStartTime; - const runTime = new Date(deltaTime) - .toISOString() - .substr(11, 8) - .toString(); - this.statsPanel.addOrUpdateStat('DurationStat', 'Duration', runTime); - - // Input control? - const controlsStreamInput = - this.inputController === null - ? 'Not sent yet' - : this.inputController - ? 'true' - : 'false'; - this.statsPanel.addOrUpdateStat( - 'ControlsInputStat', - 'Controls stream input', - controlsStreamInput - ); - - // QP - this.statsPanel.addOrUpdateStat( - 'QPStat', - 'Video quantization parameter', - this.videoQpIndicator.videoEncoderAvgQP.toString() - ); - - // Grab all stats we can off the aggregated stats - this.statsPanel.handleStats(videoStats); - } - - /** - * Set up functionality to happen when calculating the average video encoder qp - * @param QP - the quality number of the stream - */ - onVideoEncoderAvgQP(QP: number) { - this.videoQpIndicator.updateQpTooltip(QP); - } - - /** - * Set up functionality to happen when receiving and handling initial settings for the UE app - * @param settings - initial UE app settings - */ - onInitialSettings(settings: InitialSettings) { - if (settings.PixelStreamingSettings) { - const allowConsoleCommands = - settings.PixelStreamingSettings.AllowPixelStreamingCommands; - if (allowConsoleCommands === false) { - Logger.Info( - Logger.GetStackTrace(), - '-AllowPixelStreamingCommands=false, sending arbitrary console commands from browser to UE is disabled.' - ); - } - const disableLatencyTest = - settings.PixelStreamingSettings.DisableLatencyTest; - if (disableLatencyTest) { - this.statsPanel.latencyTest.latencyTestButton.disabled = true; - this.statsPanel.latencyTest.latencyTestButton.title = - 'Disabled by -PixelStreamingDisableLatencyTester=true'; - Logger.Info( - Logger.GetStackTrace(), - '-PixelStreamingDisableLatencyTester=true, requesting latency report from the the browser to UE is disabled.' - ); - } - } - if (settings.EncoderSettings) { - this.config.setNumericSetting( - NumericParameters.MinQP, - settings.EncoderSettings.MinQP - ); - this.config.setNumericSetting( - NumericParameters.MaxQP, - settings.EncoderSettings.MaxQP - ); - } - if (settings.WebRTCSettings) { - this.config.setNumericSetting( - NumericParameters.WebRTCMinBitrate, - settings.WebRTCSettings.MinBitrate - ); - this.config.setNumericSetting( - NumericParameters.WebRTCMinBitrate, - settings.WebRTCSettings.MaxBitrate - ); - this.config.setNumericSetting( - NumericParameters.WebRTCFPS, - settings.WebRTCSettings.FPS - ); - } - } - - /** - * Set up functionality to happen when setting quality control ownership of a stream - * @param hasQualityOwnership - does this user have quality ownership of the stream true / false - */ - onQualityControlOwnership(hasQualityOwnership: boolean) { - this.config.setFlagEnabled( - Flags.IsQualityController, - hasQualityOwnership - ); - } - - /** - * Update the players color variables - * @param isLightMode - should we use a light or dark color scheme - */ - updateColors(isLightMode: boolean) { - const rootElement = document.querySelector(':root') as HTMLElement; - if (isLightMode) { - rootElement.style.setProperty('--color0', '#e2e0dd80'); - rootElement.style.setProperty('--color1', '#FFFFFF'); - rootElement.style.setProperty('--color2', '#000000'); - rootElement.style.setProperty('--color3', '#0585fe'); - rootElement.style.setProperty('--color4', '#35b350'); - rootElement.style.setProperty('--color5', '#ffab00'); - rootElement.style.setProperty('--color6', '#e1e2dd'); - rootElement.style.setProperty('--color7', '#c3c4bf'); - } else { - rootElement.style.setProperty('--color0', '#1D1F2280'); - rootElement.style.setProperty('--color1', '#000000'); - rootElement.style.setProperty('--color2', '#FFFFFF'); - rootElement.style.setProperty('--color3', '#0585fe'); - rootElement.style.setProperty('--color4', '#35b350'); - rootElement.style.setProperty('--color5', '#ffab00'); - rootElement.style.setProperty('--color6', '#1e1d22'); - rootElement.style.setProperty('--color7', '#3c3b40'); - } - } -} diff --git a/Frontend/library/src/Config/Config.ts b/Frontend/library/src/Config/Config.ts index c3ed7095..200c3d77 100644 --- a/Frontend/library/src/Config/Config.ts +++ b/Frontend/library/src/Config/Config.ts @@ -5,106 +5,168 @@ import { SettingFlag } from './SettingFlag'; import { SettingNumber } from './SettingNumber'; import { SettingText } from './SettingText'; import { SettingOption } from './SettingOption'; +import { EventEmitter, SettingsChangedEvent } from '../Util/EventEmitter'; +import { SettingBase } from './SettingBase'; /** * A collection of flags that can be toggled and are core to all Pixel Streaming experiences. - * These are used in the `Config.Flags` map. Note, that map can take any string but - * these flags are provided for convenience to avoid hardcoded strings across the library. + * These are used in the `Config.Flags` map. */ export class Flags { - static AutoConnect = 'AutoConnect'; - static AutoPlayVideo = 'AutoPlayVideo'; - static AFKDetection = 'TimeoutIfIdle'; - static BrowserSendOffer = 'OfferToReceive'; - static HoveringMouseMode = 'HoveringMouse'; - static ForceMonoAudio = 'ForceMonoAudio'; - static ForceTURN = 'ForceTURN'; - static FakeMouseWithTouches = 'FakeMouseWithTouches'; - static IsQualityController = 'ControlsQuality'; - static MatchViewportResolution = 'MatchViewportRes'; - static PreferSFU = 'preferSFU'; - static StartVideoMuted = 'StartVideoMuted'; - static SuppressBrowserKeys = 'SuppressBrowserKeys'; - static UseMic = 'UseMic'; - static LightMode = 'LightMode'; + static AutoConnect = 'AutoConnect' as const; + static AutoPlayVideo = 'AutoPlayVideo' as const; + static AFKDetection = 'TimeoutIfIdle' as const; + static BrowserSendOffer = 'OfferToReceive' as const; + static HoveringMouseMode = 'HoveringMouse' as const; + static ForceMonoAudio = 'ForceMonoAudio' as const; + static ForceTURN = 'ForceTURN' as const; + static FakeMouseWithTouches = 'FakeMouseWithTouches' as const; + static IsQualityController = 'ControlsQuality' as const; + static MatchViewportResolution = 'MatchViewportRes' as const; + static PreferSFU = 'preferSFU' as const; + static StartVideoMuted = 'StartVideoMuted' as const; + static SuppressBrowserKeys = 'SuppressBrowserKeys' as const; + static UseMic = 'UseMic' as const; } +export type FlagsKeys = Exclude; +export type FlagsIds = typeof Flags[FlagsKeys]; + +const isFlagId = (id: string): id is FlagsIds => + Object.getOwnPropertyNames(Flags).some( + (name: FlagsKeys) => Flags[name] === id + ); + /** * A collection of numeric parameters that are core to all Pixel Streaming experiences. * */ export class NumericParameters { - static AFKTimeoutSecs = 'AFKTimeout'; - static MinQP = 'MinQP'; - static MaxQP = 'MaxQP'; - static WebRTCFPS = 'WebRTCFPS'; - static WebRTCMinBitrate = 'WebRTCMinBitrate'; - static WebRTCMaxBitrate = 'WebRTCMaxBitrate'; + static AFKTimeoutSecs = 'AFKTimeout' as const; + static MinQP = 'MinQP' as const; + static MaxQP = 'MaxQP' as const; + static WebRTCFPS = 'WebRTCFPS' as const; + static WebRTCMinBitrate = 'WebRTCMinBitrate' as const; + static WebRTCMaxBitrate = 'WebRTCMaxBitrate' as const; } +export type NumericParametersKeys = Exclude< + keyof typeof NumericParameters, + 'prototype' +>; +export type NumericParametersIds = + typeof NumericParameters[NumericParametersKeys]; + +const isNumericId = (id: string): id is NumericParametersIds => + Object.getOwnPropertyNames(NumericParameters).some( + (name: NumericParametersKeys) => NumericParameters[name] === id + ); + /** * A collection of textual parameters that are core to all Pixel Streaming experiences. * */ export class TextParameters { - static SignallingServerUrl = 'ss'; + static SignallingServerUrl = 'ss' as const; } +export type TextParametersKeys = Exclude< + keyof typeof TextParameters, + 'prototype' +>; +export type TextParametersIds = typeof TextParameters[TextParametersKeys]; + +const isTextId = (id: string): id is TextParametersIds => + Object.getOwnPropertyNames(TextParameters).some( + (name: TextParametersKeys) => TextParameters[name] === id + ); + /** * A collection of enum based parameters that are core to all Pixel Streaming experiences. * */ export class OptionParameters { - static PreferredCodec = 'PreferredCodec'; - static StreamerId = 'StreamerId'; + static PreferredCodec = 'PreferredCodec' as const; + static StreamerId = 'StreamerId' as const; } +export type OptionParametersKeys = Exclude< + keyof typeof OptionParameters, + 'prototype' +>; +export type OptionParametersIds = typeof OptionParameters[OptionParametersKeys]; + +const isOptionId = (id: string): id is OptionParametersIds => + Object.getOwnPropertyNames(OptionParameters).some( + (name: OptionParametersKeys) => OptionParameters[name] === id + ); + +/** + * Utility types for inferring data type based on setting ID + */ +export type OptionIds = + | FlagsIds + | NumericParametersIds + | TextParametersIds + | OptionParametersIds; +export type OptionKeys = T extends FlagsIds + ? boolean + : T extends NumericParametersIds + ? number + : T extends TextParametersIds + ? string + : T extends OptionParametersIds + ? string + : never; + +export type AllSettings = { + [K in OptionIds]: OptionKeys; +}; + +export interface ConfigParams { + /** Initial Pixel Streaming settings */ + initialSettings?: Partial; + /** If useUrlParams is set true, will read initial values from URL parameters and persist changed settings into URL */ + useUrlParams?: boolean; +} export class Config { /* A map of flags that can be toggled - options that can be set in the application - e.g. Use Mic? */ - private flags = new Map(); + private flags = new Map(); /* A map of numerical settings - options that can be in the application - e.g. MinBitrate */ - private numericParameters = new Map(); + private numericParameters = new Map(); /* A map of text settings - e.g. signalling server url */ - private textParameters = new Map(); + private textParameters = new Map(); /* A map of enum based settings - e.g. preferred codec */ - private optionParameters = new Map(); + private optionParameters = new Map(); + + private _useUrlParams: boolean; // ------------ Settings ----------------- - constructor() { - this.populateDefaultSettings(); + constructor(config: ConfigParams = {}) { + const { initialSettings, useUrlParams } = config; + this._useUrlParams = !!useUrlParams; + this.populateDefaultSettings(this._useUrlParams); + if (initialSettings) { + this.setSettings(initialSettings); + } } /** - * Make DOM elements for a settings section with a heading. - * @param settingsElem The parent container for our DOM elements. - * @param sectionHeading The heading element to go into the section. - * @returns The constructed DOM element for the section. + * True if reading configuration initial values from URL parameters, and + * persisting changes in URL when changed. */ - buildSectionWithHeading(settingsElem: HTMLElement, sectionHeading: string) { - // make section element - const sectionElem = document.createElement('section'); - sectionElem.classList.add('settingsContainer'); - - // make section heading - const psSettingsHeader = document.createElement('div'); - psSettingsHeader.classList.add('settingsHeader'); - psSettingsHeader.classList.add('settings-text'); - psSettingsHeader.textContent = sectionHeading; - - // add section and heading to parent settings element - sectionElem.appendChild(psSettingsHeader); - settingsElem.appendChild(sectionElem); - return sectionElem; + public get useUrlParams() { + return this._useUrlParams; } /** * Populate the default settings for a Pixel Streaming application */ - populateDefaultSettings(): void { + private populateDefaultSettings(useUrlParams: boolean): void { /** * Text Parameters */ @@ -121,7 +183,8 @@ export class Config { (window.location.port === '80' || window.location.port === '' ? '' - : `:${window.location.port}`) + : `:${window.location.port}`), + useUrlParams ) ); @@ -132,7 +195,8 @@ export class Config { 'Streamer ID', 'The ID of the streamer to stream.', '', - [] + [], + useUrlParams ) ); @@ -168,7 +232,8 @@ export class Config { } }); return browserSupportedCodecs; - })() + })(), + useUrlParams ) ); @@ -182,7 +247,8 @@ export class Config { Flags.AutoConnect, 'Auto connect to stream', 'Whether we should attempt to auto connect to the signalling server or show a click to start prompt.', - false + false, + useUrlParams ) ); @@ -192,7 +258,8 @@ export class Config { Flags.AutoPlayVideo, 'Auto play video', 'When video is ready automatically start playing it as opposed to showing a play button.', - true + true, + useUrlParams ) ); @@ -202,7 +269,8 @@ export class Config { Flags.BrowserSendOffer, 'Browser send offer', 'Browser will initiate the WebRTC handshake by sending the offer to the streamer', - false + false, + useUrlParams ) ); @@ -212,7 +280,8 @@ export class Config { Flags.UseMic, 'Use microphone', 'Make browser request microphone access and open an input audio track.', - false + false, + useUrlParams ) ); @@ -222,7 +291,8 @@ export class Config { Flags.StartVideoMuted, 'Start video muted', 'Video will start muted if true.', - false + false, + useUrlParams ) ); @@ -232,7 +302,8 @@ export class Config { Flags.SuppressBrowserKeys, 'Suppress browser keys', 'Suppress certain browser keys that we use in UE, for example F5 to show shader complexity instead of refresh the page.', - true + true, + useUrlParams ) ); @@ -242,7 +313,8 @@ export class Config { Flags.PreferSFU, 'Prefer SFU', 'Try to connect to the SFU instead of P2P.', - false + false, + useUrlParams ) ); @@ -252,7 +324,8 @@ export class Config { Flags.IsQualityController, 'Is quality controller?', 'True if this peer controls stream quality', - true + true, + useUrlParams ) ); @@ -262,7 +335,8 @@ export class Config { Flags.ForceMonoAudio, 'Force mono audio', 'Force browser to request mono audio in the SDP', - false + false, + useUrlParams ) ); @@ -272,7 +346,8 @@ export class Config { Flags.ForceTURN, 'Force TURN', 'Only generate TURN/Relayed ICE candidates.', - false + false, + useUrlParams ) ); @@ -282,7 +357,8 @@ export class Config { Flags.AFKDetection, 'AFK if idle', 'Timeout the experience if user is AFK for a period.', - false + false, + useUrlParams ) ); @@ -292,7 +368,8 @@ export class Config { Flags.MatchViewportResolution, 'Match viewport resolution', 'Pixel Streaming will be instructed to dynamically resize the video stream to match the size of the video element.', - false + false, + useUrlParams ) ); @@ -302,7 +379,11 @@ export class Config { Flags.HoveringMouseMode, 'Control Scheme: Locked Mouse', 'Either locked mouse, where the pointer is consumed by the video and locked to it, or hovering mouse, where the mouse is not consumed.', - false + false, + useUrlParams, + (isHoveringMouse: boolean, setting: SettingBase) => { + setting.label = `Control Scheme: ${isHoveringMouse ? 'Hovering' : 'Locked'} Mouse`; + } ) ); @@ -312,17 +393,8 @@ export class Config { Flags.FakeMouseWithTouches, 'Fake mouse with touches', 'A single finger touch is converted into a mouse event. This allows a non-touch application to be controlled partially via a touch device.', - false - ) - ); - - this.flags.set( - Flags.LightMode, - new SettingFlag( - Flags.LightMode, - 'Use a light color scheme', - 'The Pixel Streaming player will be instructed to use a lighter color scheme', - false // (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches would use system preference + false, + useUrlParams ) ); @@ -338,7 +410,8 @@ export class Config { 'The time (in seconds) it takes for the application to time out if AFK timeout is enabled.', 0 /*min*/, 600 /*max*/, - 120 /*value*/ + 120 /*value*/, + useUrlParams ) ); @@ -350,7 +423,8 @@ export class Config { 'The lower bound for the quantization parameter (QP) of the encoder. 0 = Best quality, 51 = worst quality.', 0 /*min*/, 51 /*max*/, - 0 /*value*/ + 0 /*value*/, + useUrlParams ) ); @@ -362,7 +436,8 @@ export class Config { 'The upper bound for the quantization parameter (QP) of the encoder. 0 = Best quality, 51 = worst quality.', 0 /*min*/, 51 /*max*/, - 51 /*value*/ + 51 /*value*/, + useUrlParams ) ); @@ -374,7 +449,8 @@ export class Config { 'The maximum FPS that WebRTC will try to transmit frames at.', 1 /*min*/, 999 /*max*/, - 60 /*value*/ + 60 /*value*/, + useUrlParams ) ); @@ -385,8 +461,9 @@ export class Config { 'Min Bitrate (kbps)', 'The minimum bitrate that WebRTC should use.', 0 /*min*/, - 100000 /*max*/, - 0 /*value*/ + 500000 /*max*/, + 0 /*value*/, + useUrlParams ) ); @@ -397,145 +474,20 @@ export class Config { 'Max Bitrate (kbps)', 'The maximum bitrate that WebRTC should use.', 0 /*min*/, - 100000 /*max*/, - 0 /*value*/ + 500000 /*max*/, + 0 /*value*/, + useUrlParams ) ); } - /** - * Setup flags with their default values and add them to the `Config.flags` map. - * @param settingsElem - The element that contains all the individual settings sections, flags, and so on. - */ - populateSettingsElement(settingsElem: HTMLElement): void { - /* Setup all Pixel Streaming specific settings */ - const psSettingsSection = this.buildSectionWithHeading( - settingsElem, - 'Pixel Streaming' - ); - - // make settings show up in DOM - this.addSettingText( - psSettingsSection, - this.textParameters.get(TextParameters.SignallingServerUrl) - ); - this.addSettingOption( - psSettingsSection, - this.optionParameters.get(OptionParameters.StreamerId) - ); - this.addSettingFlag( - psSettingsSection, - this.flags.get(Flags.AutoConnect) - ); - this.addSettingFlag( - psSettingsSection, - this.flags.get(Flags.AutoPlayVideo) - ); - this.addSettingFlag( - psSettingsSection, - this.flags.get(Flags.BrowserSendOffer) - ); - this.addSettingFlag(psSettingsSection, this.flags.get(Flags.UseMic)); - this.addSettingFlag( - psSettingsSection, - this.flags.get(Flags.StartVideoMuted) - ); - this.addSettingFlag(psSettingsSection, this.flags.get(Flags.PreferSFU)); - this.addSettingFlag( - psSettingsSection, - this.flags.get(Flags.IsQualityController) - ); - this.addSettingFlag( - psSettingsSection, - this.flags.get(Flags.ForceMonoAudio) - ); - this.addSettingFlag(psSettingsSection, this.flags.get(Flags.ForceTURN)); - this.addSettingFlag( - psSettingsSection, - this.flags.get(Flags.SuppressBrowserKeys) - ); - this.addSettingFlag( - psSettingsSection, - this.flags.get(Flags.AFKDetection) - ); - this.addSettingNumeric( - psSettingsSection, - this.numericParameters.get(NumericParameters.AFKTimeoutSecs) - ); - - /* Setup all view/ui related settings under this section */ - const viewSettingsSection = this.buildSectionWithHeading( - settingsElem, - 'UI' - ); - this.addSettingFlag( - viewSettingsSection, - this.flags.get(Flags.MatchViewportResolution) - ); - - const ControlSchemeFlag = this.flags.get(Flags.HoveringMouseMode); - this.addSettingFlag(viewSettingsSection, ControlSchemeFlag); - ControlSchemeFlag.label = `Control Scheme: ${ - ControlSchemeFlag.flag ? 'Hovering' : 'Locked' - } Mouse`; - - const colorSchemeFlag = this.flags.get(Flags.LightMode); - this.addSettingFlag(viewSettingsSection, colorSchemeFlag); - colorSchemeFlag.label = `Color Scheme: ${ - colorSchemeFlag.flag ? 'Light' : 'Dark' - } Mode`; - - /* Setup all encoder related settings under this section */ - const encoderSettingsSection = this.buildSectionWithHeading( - settingsElem, - 'Encoder' - ); - - this.addSettingNumeric( - encoderSettingsSection, - this.numericParameters.get(NumericParameters.MinQP) - ); - this.addSettingNumeric( - encoderSettingsSection, - this.numericParameters.get(NumericParameters.MaxQP) - ); - - const preferredCodecOption = this.optionParameters.get(OptionParameters.PreferredCodec); - this.addSettingOption( - encoderSettingsSection, - preferredCodecOption - ); - if([...preferredCodecOption.selector.options].map(o => o.value).includes("Only available on Chrome")) { - preferredCodecOption.disable(); - } - - /* Setup all webrtc related settings under this section */ - const webrtcSettingsSection = this.buildSectionWithHeading( - settingsElem, - 'WebRTC' - ); - - this.addSettingNumeric( - webrtcSettingsSection, - this.numericParameters.get(NumericParameters.WebRTCFPS) - ); - this.addSettingNumeric( - webrtcSettingsSection, - this.numericParameters.get(NumericParameters.WebRTCMinBitrate) - ); - this.addSettingNumeric( - webrtcSettingsSection, - this.numericParameters.get(NumericParameters.WebRTCMaxBitrate) - ); - } - /** * Add a callback to fire when the numeric setting is toggled. * @param id The id of the flag. * @param onChangedListener The callback to fire when the numeric value changes. */ - addOnNumericSettingChangedListener( - id: string, + _addOnNumericSettingChangedListener( + id: NumericParametersIds, onChangedListener: (newValue: number) => void ): void { if (this.numericParameters.has(id)) { @@ -545,8 +497,8 @@ export class Config { } } - addOnOptionSettingChangedListener( - id: string, + _addOnOptionSettingChangedListener( + id: OptionParametersIds, onChangedListener: (newValue: string) => void ): void { if (this.optionParameters.has(id)) { @@ -560,7 +512,7 @@ export class Config { * @param id The id of the numeric setting we are interested in getting a value for. * @returns The numeric value stored in the parameter with the passed id. */ - getNumericSettingValue(id: string): number { + getNumericSettingValue(id: NumericParametersIds): number { if (this.numericParameters.has(id)) { return this.numericParameters.get(id).number; } else { @@ -572,7 +524,7 @@ export class Config { * @param id The id of the text setting we are interested in getting a value for. * @returns The text value stored in the parameter with the passed id. */ - getTextSettingValue(id: string): string { + getTextSettingValue(id: TextParametersIds): string { if (this.textParameters.has(id)) { return this.textParameters.get(id).value as string; } else { @@ -585,7 +537,7 @@ export class Config { * @param id The id of the numeric setting we are interested in. * @param value The numeric value to set. */ - setNumericSetting(id: string, value: number): void { + setNumericSetting(id: NumericParametersIds, value: number): void { if (this.numericParameters.has(id)) { this.numericParameters.get(id).number = value; } else { @@ -598,8 +550,8 @@ export class Config { * @param id The id of the flag. * @param onChangeListener The callback to fire when the value changes. */ - addOnSettingChangedListener( - id: string, + _addOnSettingChangedListener( + id: FlagsIds, onChangeListener: (newFlagValue: boolean) => void ): void { if (this.flags.has(id)) { @@ -612,8 +564,8 @@ export class Config { * @param id The id of the flag. * @param onChangeListener The callback to fire when the value changes. */ - addOnTextSettingChangedListener( - id: string, + _addOnTextSettingChangedListener( + id: TextParametersIds, onChangeListener: (newTextValue: string) => void ): void { if (this.textParameters.has(id)) { @@ -622,58 +574,11 @@ export class Config { } /** - * Add a SettingText element to a particular settings section in the DOM and registers that text in the text settings map. - * @param settingsSection The settings section HTML element. - * @param settingText The textual settings object. + * Get the option which has the given id. + * @param id The id of the option. + * @returns The SettingOption object matching id */ - addSettingText( - settingsSection: HTMLElement, - settingText: SettingText - ): void { - settingsSection.appendChild(settingText.rootElement); - this.textParameters.set(settingText.id, settingText); - } - - /** - * Add a SettingFlag element to a particular settings section in the DOM and registers that flag in the Config.flag map. - * @param settingsSection The settings section HTML element. - * @param settingFlag The settings flag object. - */ - addSettingFlag( - settingsSection: HTMLElement, - settingFlag: SettingFlag - ): void { - settingsSection.appendChild(settingFlag.rootElement); - this.flags.set(settingFlag.id, settingFlag); - } - - /** - * Add a numeric setting element to a particular settings section in the DOM and registers that flag in the Config.numericParameters map. - * @param settingsSection The settings section HTML element. - * @param settingFlag The settings flag object. - */ - addSettingNumeric( - settingsSection: HTMLElement, - setting: SettingNumber - ): void { - settingsSection.appendChild(setting.rootElement); - this.numericParameters.set(setting.id, setting); - } - - /** - * Add an enum based settings element to a particular settings section in the DOM and registers that flag in the Config.enumParameters map. - * @param settingsSection The settings section HTML element. - * @param settingFlag The settings flag object. - */ - addSettingOption( - settingsSection: HTMLElement, - setting: SettingOption - ): void { - settingsSection.appendChild(setting.rootElement); - this.optionParameters.set(setting.id, setting); - } - - getSettingOption(id: string): SettingOption { + getSettingOption(id: OptionParametersIds): SettingOption { return this.optionParameters.get(id); } @@ -682,7 +587,7 @@ export class Config { * @param id The unique id for the flag. * @returns True if the flag is enabled. */ - isFlagEnabled(id: string): boolean { + isFlagEnabled(id: FlagsIds): boolean { return this.flags.get(id).flag as boolean; } @@ -691,7 +596,7 @@ export class Config { * @param id The id of the flag to toggle. * @param flagEnabled True if the flag should be enabled. */ - setFlagEnabled(id: string, flagEnabled: boolean) { + setFlagEnabled(id: FlagsIds, flagEnabled: boolean) { if (!this.flags.has(id)) { Logger.Warning( Logger.GetStackTrace(), @@ -707,7 +612,7 @@ export class Config { * @param id The id of the setting * @param settingValue The value to set in the setting. */ - setTextSetting(id: string, settingValue: string) { + setTextSetting(id: TextParametersIds, settingValue: string) { if (!this.textParameters.has(id)) { Logger.Warning( Logger.GetStackTrace(), @@ -723,7 +628,10 @@ export class Config { * @param id The id of the setting * @param settingOptions The values the setting could take */ - setOptionSettingOptions(id: string, settingOptions: Array) { + setOptionSettingOptions( + id: OptionParametersIds, + settingOptions: Array + ) { if (!this.optionParameters.has(id)) { Logger.Warning( Logger.GetStackTrace(), @@ -739,7 +647,7 @@ export class Config { * @param id The id of the setting * @param settingOptions The value to select out of all the options */ - setOptionSettingValue(id: string, settingValue: string) { + setOptionSettingValue(id: OptionParametersIds, settingValue: string) { if (!this.optionParameters.has(id)) { Logger.Warning( Logger.GetStackTrace(), @@ -755,7 +663,7 @@ export class Config { * @param id The id of the flag. * @param label The new label to use for the flag. */ - setFlagLabel(id: string, label: string) { + setFlagLabel(id: FlagsIds, label: string) { if (!this.flags.has(id)) { Logger.Warning( Logger.GetStackTrace(), @@ -765,6 +673,141 @@ export class Config { this.flags.get(id).label = label; } } + + /** + * Set a subset of all settings in one function call. + * + * @param settings A (partial) list of settings to set + */ + setSettings(settings: Partial) { + for (const key of Object.keys(settings)) { + if (isFlagId(key)) { + this.setFlagEnabled(key, settings[key]); + } else if (isNumericId(key)) { + this.setNumericSetting(key, settings[key]); + } else if (isTextId(key)) { + this.setTextSetting(key, settings[key]); + } else if (isOptionId(key)) { + this.setOptionSettingValue(key, settings[key]); + } + } + } + + /** + * Get all settings + * @returns All setting values as an object with setting ids as keys + */ + getSettings(): Partial { + const settings: Partial = {}; + for (const [key, value] of this.flags.entries()) { + settings[key] = value.flag; + } + for (const [key, value] of this.numericParameters.entries()) { + settings[key] = value.number; + } + for (const [key, value] of this.textParameters.entries()) { + settings[key] = value.text; + } + for (const [key, value] of this.optionParameters.entries()) { + settings[key] = value.selected; + } + return settings; + } + + /** + * Get all Flag settings as an array. + * @returns All SettingFlag objects + */ + getFlags(): Array { + return Array.from(this.flags.values()); + } + + /** + * Get all Text settings as an array. + * @returns All SettingText objects + */ + getTextSettings(): Array { + return Array.from(this.textParameters.values()); + } + + /** + * Get all Number settings as an array. + * @returns All SettingNumber objects + */ + getNumericSettings(): Array { + return Array.from(this.numericParameters.values()); + } + + /** + * Get all Option settings as an array. + * @returns All SettingOption objects + */ + getOptionSettings(): Array { + return Array.from(this.optionParameters.values()); + } + + /** + * Emit events when settings change. + * @param eventEmitter + */ + _registerOnChangeEvents(eventEmitter: EventEmitter) { + for (const key of this.flags.keys()) { + const flag = this.flags.get(key); + if (flag) { + flag.onChangeEmit = (newValue: boolean) => + eventEmitter.dispatchEvent( + new SettingsChangedEvent({ + id: flag.id, + type: 'flag', + value: newValue, + target: flag + }) + ); + } + } + for (const key of this.numericParameters.keys()) { + const number = this.numericParameters.get(key); + if (number) { + number.onChangeEmit = (newValue: number) => + eventEmitter.dispatchEvent( + new SettingsChangedEvent({ + id: number.id, + type: 'number', + value: newValue, + target: number + }) + ); + } + } + for (const key of this.textParameters.keys()) { + const text = this.textParameters.get(key); + if (text) { + text.onChangeEmit = (newValue: string) => + eventEmitter.dispatchEvent( + new SettingsChangedEvent({ + id: text.id, + type: 'text', + value: newValue, + target: text + }) + ); + } + } + for (const key of this.optionParameters.keys()) { + const option = this.optionParameters.get(key); + if (option) { + option.onChangeEmit = (newValue: string) => + eventEmitter.dispatchEvent( + new SettingsChangedEvent({ + id: option.id, + type: 'option', + value: newValue, + target: option + }) + ); + } + } + } } /** diff --git a/Frontend/library/src/Config/SettingBase.ts b/Frontend/library/src/Config/SettingBase.ts index abb4d80d..36fd0d78 100644 --- a/Frontend/library/src/Config/SettingBase.ts +++ b/Frontend/library/src/Config/SettingBase.ts @@ -1,23 +1,27 @@ // Copyright Epic Games, Inc. All Rights Reserved. /** - * Base class for a setting that has a text label, an arbitrary setting value it stores, an a HTML element that represents this setting. + * Base class for a setting that has a text label and an arbitrary setting value it stores. */ export class SettingBase { id: string; description: string; _label: string; _value: unknown; - _rootElement: HTMLElement; - onChange: (changedValue: unknown) => void; + onChange: (changedValue: unknown, setting: SettingBase) => void; + onChangeEmit: (changedValue: unknown) => void; constructor( id: string, label: string, description: string, - defaultSettingValue: unknown + defaultSettingValue: unknown, + // eslint-disable-next-line @typescript-eslint/no-empty-function + defaultOnChangeListener: (changedValue: unknown, setting: SettingBase) => void = () => { /* Do nothing, to be overridden. */ } ) { - this.onChange = () => { + this.onChange = defaultOnChangeListener; + + this.onChangeEmit = () => { /* Do nothing, to be overridden. */ }; this.id = id; @@ -32,6 +36,7 @@ export class SettingBase { */ public set label(inLabel: string) { this._label = inLabel; + this.onChangeEmit(this._value); } /** @@ -54,16 +59,7 @@ export class SettingBase { */ public set value(inValue: unknown) { this._value = inValue; - this.onChange(this._value); - } - - /** - * @returns Return or creates a HTML element that represents this setting in the DOM. - */ - public get rootElement(): HTMLElement { - if (!this._rootElement) { - this._rootElement = document.createElement('div'); - } - return this._rootElement; + this.onChange(this._value, this); + this.onChangeEmit(this._value); } } diff --git a/Frontend/library/src/Config/SettingFlag.ts b/Frontend/library/src/Config/SettingFlag.ts index 737e8589..993a2ccc 100644 --- a/Frontend/library/src/Config/SettingFlag.ts +++ b/Frontend/library/src/Config/SettingFlag.ts @@ -1,30 +1,38 @@ // Copyright Epic Games, Inc. All Rights Reserved. +import type { FlagsIds } from './Config'; import { SettingBase } from './SettingBase'; -export class SettingFlag extends SettingBase { - /* We toggle this checkbox to reflect the value of our setting's boolean flag. */ - _checkbox: HTMLInputElement; // input type="checkbox" - - /* This element contains a text node that reflects the setting's text label. */ - _settingsTextElem: HTMLElement; +/** + * A boolean flag setting object with a text label. + */ +export class SettingFlag< + CustomIds extends string = FlagsIds +> extends SettingBase { + id: FlagsIds | CustomIds; + onChangeEmit: (changedValue: boolean) => void; + useUrlParams: boolean; constructor( - id: string, + id: FlagsIds | CustomIds, label: string, description: string, - defaultFlagValue: boolean + defaultFlagValue: boolean, + useUrlParams: boolean, + // eslint-disable-next-line @typescript-eslint/no-empty-function + defaultOnChangeListener: (changedValue: unknown, setting: SettingBase) => void = () => { /* Do nothing, to be overridden. */ } ) { - super(id, label, description, defaultFlagValue); + super(id, label, description, defaultFlagValue, defaultOnChangeListener); const urlParams = new URLSearchParams(window.location.search); - if (!urlParams.has(this.id)) { + if (!useUrlParams || !urlParams.has(this.id)) { this.flag = defaultFlagValue; } else { // parse flag from url parameters const urlParamFlag = this.getUrlParamFlag(); this.flag = urlParamFlag; } + this.useUrlParams = useUrlParams; } /** @@ -45,71 +53,26 @@ export class SettingFlag extends SettingBase { return false; } - public get settingsTextElem(): HTMLElement { - if (!this._settingsTextElem) { - this._settingsTextElem = document.createElement('div'); - this._settingsTextElem.innerText = this._label; - this._settingsTextElem.title = this.description; - } - return this._settingsTextElem; - } - - public get checkbox(): HTMLInputElement { - if (!this._checkbox) { - this._checkbox = document.createElement('input'); - this._checkbox.type = 'checkbox'; - } - return this._checkbox; - } - /** - * @returns Return or creates a HTML element that represents this setting in the DOM. + * Persist the setting value in URL. */ - public get rootElement(): HTMLElement { - if (!this._rootElement) { - // create root div with "setting" css class - this._rootElement = document.createElement('div'); - this._rootElement.id = this.id; - this._rootElement.classList.add('setting'); - - // create div element to contain our setting's text - this._rootElement.appendChild(this.settingsTextElem); - - // create label element to wrap out input type - const wrapperLabel = document.createElement('label'); - wrapperLabel.classList.add('tgl-switch'); - this._rootElement.appendChild(wrapperLabel); - - // create input type=checkbox - this.checkbox.title = this.description; - this.checkbox.classList.add('tgl'); - this.checkbox.classList.add('tgl-flat'); - const slider = document.createElement('div'); - slider.classList.add('tgl-slider'); - wrapperLabel.appendChild(this.checkbox); - wrapperLabel.appendChild(slider); - - // setup on change from checkbox - this.checkbox.addEventListener('change', () => { - this.flag = this.checkbox.checked; - - // set url params - const urlParams = new URLSearchParams(window.location.search); - if (this.checkbox.checked === true) { - urlParams.set(this.id, 'true'); - } else { - urlParams.delete(this.id); - } - window.history.replaceState( - {}, - '', - urlParams.toString() !== '' - ? `${location.pathname}?${urlParams}` - : `${location.pathname}` - ); - }); + public updateURLParams() { + if (this.useUrlParams) { + // set url params + const urlParams = new URLSearchParams(window.location.search); + if (this.flag === true) { + urlParams.set(this.id, 'true'); + } else { + urlParams.set(this.id, 'false'); + } + window.history.replaceState( + {}, + '', + urlParams.toString() !== '' + ? `${location.pathname}?${urlParams}` + : `${location.pathname}` + ); } - return this._rootElement; } /** @@ -123,33 +86,14 @@ export class SettingFlag extends SettingBase { * @return The setting's value. */ public get flag(): boolean { - return this.checkbox.checked; + return !!this.value; } /** * Update the setting's stored value. * @param inValue The new value for the setting. */ - public set flag(inValue: unknown) { + public set flag(inValue: boolean) { this.value = inValue; - if (typeof inValue === 'boolean') { - this.checkbox.checked = inValue; - } - } - - /** - * Set the label text for the setting. - * @param label setting label. - */ - public set label(inLabel: string) { - this._label = inLabel; - this.settingsTextElem.innerText = this._label; - } - - /** - * @returns The label text for the setting. - */ - public get label(): string { - return this._label; } } diff --git a/Frontend/library/src/Config/SettingNumber.ts b/Frontend/library/src/Config/SettingNumber.ts index 85df3e8d..9c0979be 100644 --- a/Frontend/library/src/Config/SettingNumber.ts +++ b/Frontend/library/src/Config/SettingNumber.ts @@ -1,33 +1,40 @@ // Copyright Epic Games, Inc. All Rights Reserved. -import { Logger } from '../Logger/Logger'; +import type { NumericParametersIds } from './Config'; import { SettingBase } from './SettingBase'; /** - * A number spinner with a text label beside it. + * A number setting object with a text label. Min and max limit the range of allowed values. */ -export class SettingNumber extends SettingBase { +export class SettingNumber< + CustomIds extends string = NumericParametersIds +> extends SettingBase { _min: number; _max: number; - _rootElement: HTMLElement; - _spinner: HTMLInputElement; + + id: NumericParametersIds | CustomIds; + onChangeEmit: (changedValue: number) => void; + useUrlParams: boolean; constructor( - id: string, + id: NumericParametersIds | CustomIds, label: string, description: string, min: number, max: number, - defaultNumber: number + defaultNumber: number, + useUrlParams: boolean, + // eslint-disable-next-line @typescript-eslint/no-empty-function + defaultOnChangeListener: (changedValue: unknown, setting: SettingBase) => void = () => { /* Do nothing, to be overridden. */ } ) { - super(id, label, description, defaultNumber); + super(id, label, description, defaultNumber, defaultOnChangeListener); this._min = min; this._max = max; // attempt to read the number from the url params const urlParams = new URLSearchParams(window.location.search); - if (!urlParams.has(this.id)) { + if (!useUrlParams || !urlParams.has(this.id)) { this.number = defaultNumber; } else { const parsedValue = Number.parseInt(urlParams.get(this.id)); @@ -35,36 +42,36 @@ export class SettingNumber extends SettingBase { ? defaultNumber : parsedValue; } + this.useUrlParams = useUrlParams; + } - // setup onchange - this.spinner.onchange = (event: Event) => { - const inputElem = event.target as HTMLInputElement; - - const parsedValue = Number.parseInt(inputElem.value); - - if (Number.isNaN(parsedValue)) { - Logger.Warning( - Logger.GetStackTrace(), - `Could not parse value change into a valid number - value was ${inputElem.value}, resetting value to ${this._min}` - ); - this.number = this._min; - } else { - this.number = parsedValue; - this.updateURLParams(); - } - }; + /** + * Persist the setting value in URL. + */ + public updateURLParams(): void { + if (this.useUrlParams) { + // set url params like ?id=number + const urlParams = new URLSearchParams(window.location.search); + urlParams.set(this.id, this.number.toString()); + window.history.replaceState( + {}, + '', + urlParams.toString() !== '' + ? `${location.pathname}?${urlParams}` + : `${location.pathname}` + ); + } } /** - * Set the number in the spinner (will be clamped within range). + * Set the number value (will be clamped within range). */ public set number(newNumber: number) { this.value = this.clamp(newNumber); - this.spinner.value = this.value.toString(); } /** - * @returns The number stored in the spinner. + * @returns The number stored. */ public get number(): number { return this.value as number; @@ -80,60 +87,25 @@ export class SettingNumber extends SettingBase { } /** - * Add a change listener to the spinner element. + * Returns the minimum value + * @returns The minimum value */ - public addOnChangedListener(onChangedFunc: (newNumber: number) => void) { - this.onChange = onChangedFunc; - } - - public updateURLParams(): void { - // set url params like ?id=number - const urlParams = new URLSearchParams(window.location.search); - urlParams.set(this.id, this.value.toString()); - window.history.replaceState( - {}, - '', - urlParams.toString() !== '' - ? `${location.pathname}?${urlParams}` - : `${location.pathname}` - ); + public get min(): number { + return this._min; } /** - * Get the HTMLInputElement for the button. + * Returns the maximum value + * @returns The maximum value */ - public get spinner(): HTMLInputElement { - if (!this._spinner) { - this._spinner = document.createElement('input'); - this._spinner.type = 'number'; - this._spinner.min = this._min.toString(); - this._spinner.max = this._max.toString(); - this._spinner.value = this.value.toString(); - this._spinner.title = this.description; - this._spinner.classList.add('form-control'); - } - return this._spinner; + public get max(): number { + return this._max; } /** - * @returns Return or creates a HTML element that represents this setting in the DOM. + * Add a change listener to the number object. */ - public get rootElement(): HTMLElement { - if (!this._rootElement) { - // create root div with "setting" css class - this._rootElement = document.createElement('div'); - this._rootElement.classList.add('setting'); - this._rootElement.classList.add('form-group'); - - // create div element to contain our setting's text - const settingsTextElem = document.createElement('label'); - settingsTextElem.innerText = this._label; - settingsTextElem.title = this.description; - this._rootElement.appendChild(settingsTextElem); - - // create label element to wrap out input type - this._rootElement.appendChild(this.spinner); - } - return this._rootElement; + public addOnChangedListener(onChangedFunc: (newNumber: number) => void) { + this.onChange = onChangedFunc; } } diff --git a/Frontend/library/src/Config/SettingOption.ts b/Frontend/library/src/Config/SettingOption.ts index 88062cdb..3b682fbe 100644 --- a/Frontend/library/src/Config/SettingOption.ts +++ b/Frontend/library/src/Config/SettingOption.ts @@ -1,96 +1,39 @@ // Copyright Epic Games, Inc. All Rights Reserved. +import type { OptionParametersIds } from './Config'; import { SettingBase } from './SettingBase'; -export class SettingOption extends SettingBase { - /* A select element that reflects the value of this setting. */ - _selector: HTMLSelectElement; // - - /* This element contains a text node that reflects the setting's text label. */ - _settingsTextElem: HTMLElement; +/** + * An Option setting object with a text label. Allows you to specify an array of options and select one of them. + */ +export class SettingOption< + CustomIds extends string = OptionParametersIds +> extends SettingBase { + id: OptionParametersIds | CustomIds; + onChangeEmit: (changedValue: string) => void; + _options: Array; + useUrlParams: boolean; constructor( - id: string, + id: OptionParametersIds | CustomIds, label: string, description: string, defaultTextValue: string, - options: Array + options: Array, + useUrlParams: boolean, + // eslint-disable-next-line @typescript-eslint/no-empty-function + defaultOnChangeListener: (changedValue: unknown, setting: SettingBase) => void = () => { /* Do nothing, to be overridden. */ } ) { - super(id, label, description, [defaultTextValue, defaultTextValue]); + super(id, label, description, [defaultTextValue, defaultTextValue], defaultOnChangeListener); this.options = options; - const urlParams = new URLSearchParams(window.location.search); - const stringToMatch: string = urlParams.has(this.id) - ? this.getUrlParamText() - : defaultTextValue; + const stringToMatch: string = + useUrlParams && urlParams.has(this.id) + ? this.getUrlParamText() + : defaultTextValue; this.selected = stringToMatch; - } - - public get selector(): HTMLSelectElement { - if (!this._selector) { - this._selector = document.createElement('select'); - this._selector.classList.add('form-control'); - } - return this._selector; - } - - public get settingsTextElem(): HTMLElement { - if (!this._settingsTextElem) { - this._settingsTextElem = document.createElement('div'); - this._settingsTextElem.innerText = this._label; - this._settingsTextElem.title = this.description; - } - return this._settingsTextElem; - } - - /** - * Set the label text for the setting. - * @param label setting label. - */ - public set label(inLabel: string) { - this._label = inLabel; - this.settingsTextElem.innerText = this._label; - } - - /** - * @returns Return or creates a HTML element that represents this setting in the DOM. - */ - public get rootElement(): HTMLElement { - if (!this._rootElement) { - // create root div with "setting" css class - this._rootElement = document.createElement('div'); - this._rootElement.id = this.id; - this._rootElement.classList.add('setting'); - - // create div element to contain our setting's text - this._rootElement.appendChild(this.settingsTextElem); - - // create label element to wrap out input type - const wrapperLabel = document.createElement('label'); - this._rootElement.appendChild(wrapperLabel); - - // create select element - this.selector.title = this.description; - wrapperLabel.appendChild(this.selector); - - // setup on change from selector - this.selector.onchange = () => { - this.value = this.selector.value; - - // set url params - const urlParams = new URLSearchParams(window.location.search); - urlParams.set(this.id, this.selector.value); - window.history.replaceState( - {}, - '', - urlParams.toString() !== '' - ? `${location.pathname}?${urlParams}` - : `${location.pathname}` - ); - }; - } - return this._rootElement; + this.useUrlParams = useUrlParams; } /** @@ -100,11 +43,29 @@ export class SettingOption extends SettingBase { getUrlParamText(): string { const urlParams = new URLSearchParams(window.location.search); if (urlParams.has(this.id)) { - return urlParams.get(this.id); + return urlParams.get(this.id) ?? ''; } return ''; } + /** + * Persist the setting value in URL. + */ + public updateURLParams() { + if (this.useUrlParams) { + // set url params + const urlParams = new URLSearchParams(window.location.search); + urlParams.set(this.id, this.selected); + window.history.replaceState( + {}, + '', + urlParams.toString() !== '' + ? `${location.pathname}?${urlParams}` + : `${location.pathname}` + ); + } + } + /** * Add a change listener to the select element. */ @@ -112,27 +73,33 @@ export class SettingOption extends SettingBase { this.onChange = onChangedFunc; } + /** + * @returns All available options as an array + */ public get options(): Array { - return [...this.selector.options].map((o) => o.value); + return this._options; } + /** + * Set options + * @param values Array of options + */ public set options(values: Array) { - for (let i = this.selector.options.length - 1; i >= 0; i--) { - this.selector.remove(i); - } - - values.forEach((value: string) => { - const opt = document.createElement('option'); - opt.value = value; - opt.innerHTML = value; - this.selector.appendChild(opt); - }); + this._options = values; + this.onChangeEmit(this.selected); } + /** + * @returns Selected option as a string + */ public get selected(): string { return this.value as string; } + /** + * Set selected option if it matches one of the available options + * @param value Selected option + */ public set selected(value: string) { // A user may not specify the full possible value so we instead use the closest match. // eg ?xxx=H264 would select 'H264 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f' @@ -141,15 +108,6 @@ export class SettingOption extends SettingBase { ); if (filteredList.length) { this.value = filteredList[0]; - this.selector.value = filteredList[0]; } } - - public disable() { - this.selector.disabled = true; - } - - public enable() { - this.selector.disabled = false; - } } diff --git a/Frontend/library/src/Config/SettingText.ts b/Frontend/library/src/Config/SettingText.ts index ac0fe39e..386d3c95 100644 --- a/Frontend/library/src/Config/SettingText.ts +++ b/Frontend/library/src/Config/SettingText.ts @@ -1,30 +1,38 @@ // Copyright Epic Games, Inc. All Rights Reserved. +import type { TextParametersIds } from './Config'; import { SettingBase } from './SettingBase'; -export class SettingText extends SettingBase { - /* A text box that reflects the value of this setting. */ - _textbox: HTMLInputElement; // input type="text" - - /* This element contains a text node that reflects the setting's text label. */ - _settingsTextElem: HTMLElement; +/** + * A text setting object with a text label. + */ +export class SettingText< + CustomIds extends string = TextParametersIds +> extends SettingBase { + id: TextParametersIds | CustomIds; + onChangeEmit: (changedValue: string) => void; + useUrlParams: boolean; constructor( - id: string, + id: TextParametersIds | CustomIds, label: string, description: string, - defaultTextValue: string + defaultTextValue: string, + useUrlParams: boolean, + // eslint-disable-next-line @typescript-eslint/no-empty-function + defaultOnChangeListener: (changedValue: unknown, setting: SettingBase) => void = () => { /* Do nothing, to be overridden. */ } ) { - super(id, label, description, defaultTextValue); + super(id, label, description, defaultTextValue, defaultOnChangeListener); const urlParams = new URLSearchParams(window.location.search); - if (!urlParams.has(this.id)) { + if (!useUrlParams || !urlParams.has(this.id)) { this.text = defaultTextValue; } else { // parse flag from url parameters const urlParamFlag = this.getUrlParamText(); this.text = urlParamFlag; } + this.useUrlParams = useUrlParams; } /** @@ -34,93 +42,41 @@ export class SettingText extends SettingBase { getUrlParamText(): string { const urlParams = new URLSearchParams(window.location.search); if (urlParams.has(this.id)) { - return urlParams.get(this.id); + return urlParams.get(this.id) ?? ''; } return ''; } - public get settingsTextElem(): HTMLElement { - if (!this._settingsTextElem) { - this._settingsTextElem = document.createElement('div'); - this._settingsTextElem.innerText = this._label; - this._settingsTextElem.title = this.description; - } - return this._settingsTextElem; - } - - public get textbox(): HTMLInputElement { - if (!this._textbox) { - this._textbox = document.createElement('input'); - this._textbox.classList.add('form-control'); - this._textbox.type = 'textbox'; - } - return this._textbox; - } - /** - * @returns Return or creates a HTML element that represents this setting in the DOM. + * Persist the setting value in URL. */ - public get rootElement(): HTMLElement { - if (!this._rootElement) { - // create root div with "setting" css class - this._rootElement = document.createElement('div'); - this._rootElement.id = this.id; - this._rootElement.classList.add('setting'); - - // create div element to contain our setting's text - this._rootElement.appendChild(this.settingsTextElem); - - // create label element to wrap out input type - const wrapperLabel = document.createElement('label'); - this._rootElement.appendChild(wrapperLabel); - - // create input type=checkbox - this.textbox.title = this.description; - wrapperLabel.appendChild(this.textbox); - - // setup on change from checkbox - this.textbox.addEventListener('input', () => { - this.text = this.textbox.value; - - // set url params - const urlParams = new URLSearchParams(window.location.search); - urlParams.set(this.id, this.textbox.value); - window.history.replaceState( - {}, - '', - urlParams.toString() !== '' - ? `${location.pathname}?${urlParams}` - : `${location.pathname}` - ); - }); + public updateURLParams() { + if (this.useUrlParams) { + // set url params + const urlParams = new URLSearchParams(window.location.search); + urlParams.set(this.id, this.text); + window.history.replaceState( + {}, + '', + urlParams.toString() !== '' + ? `${location.pathname}?${urlParams}` + : `${location.pathname}` + ); } - return this._rootElement; } /** * @return The setting's value. */ public get text(): string { - return this.textbox.value; + return this.value as string; } /** * Update the setting's stored value. * @param inValue The new value for the setting. */ - public set text(inValue: unknown) { + public set text(inValue: string) { this.value = inValue; - if (typeof inValue === 'string') { - this.textbox.value = inValue; - } - } - - /** - * Set the label text for the setting. - * @param label setting label. - */ - public set label(inLabel: string) { - this._label = inLabel; - this.settingsTextElem.innerText = this._label; } } diff --git a/Frontend/library/src/DataChannel/DataChannelController.ts b/Frontend/library/src/DataChannel/DataChannelController.ts index 774ea2cf..05c7e994 100644 --- a/Frontend/library/src/DataChannel/DataChannelController.ts +++ b/Frontend/library/src/DataChannel/DataChannelController.ts @@ -49,8 +49,8 @@ export class DataChannelController { setupDataChannel() { //We Want an Array Buffer not a blob this.dataChannel.binaryType = 'arraybuffer'; - this.dataChannel.onopen = () => this.handleOnOpen(); - this.dataChannel.onclose = () => this.handleOnClose(); + this.dataChannel.onopen = (ev: Event) => this.handleOnOpen(ev); + this.dataChannel.onclose = (ev: Event) => this.handleOnClose(ev); this.dataChannel.onmessage = (ev: MessageEvent) => this.handleOnMessage(ev); this.dataChannel.onerror = (ev: MessageEvent) => this.handleOnError(ev); @@ -59,23 +59,25 @@ export class DataChannelController { /** * Handles when the Data Channel is opened */ - handleOnOpen() { + handleOnOpen(ev: Event) { Logger.Log( Logger.GetStackTrace(), `Data Channel (${this.label}) opened.`, 7 ); + this.onOpen(this.dataChannel?.label, ev); } /** * Handles when the Data Channel is closed */ - handleOnClose() { + handleOnClose(ev: Event) { Logger.Log( Logger.GetStackTrace(), `Data Channel (${this.label}) closed.`, 7 ); + this.onClose(this.dataChannel?.label, ev); } /** @@ -101,5 +103,36 @@ export class DataChannelController { `Data Channel (${this.label}) error: ${event}`, 7 ); + this.onError(this.dataChannel?.label, event); + } + + /** + * Override to register onOpen handler + * @param label Data channel label ("datachannel", "send-datachannel", "recv-datachannel") + * @param ev event + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onOpen(label: string, ev: Event) { + // empty default implementation + } + + /** + * Override to register onClose handler + * @param label Data channel label ("datachannel", "send-datachannel", "recv-datachannel") + * @param ev event + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onClose(label: string, ev: Event) { + // empty default implementation + } + + /** + * Override to register onError handler + * @param label Data channel label ("datachannel", "send-datachannel", "recv-datachannel") + * @param ev event + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onError(label: string, ev: Event) { + // empty default implementation } } diff --git a/Frontend/library/src/PeerConnectionController/AggregatedStats.ts b/Frontend/library/src/PeerConnectionController/AggregatedStats.ts index fa173d7b..9b0c99c7 100644 --- a/Frontend/library/src/PeerConnectionController/AggregatedStats.ts +++ b/Frontend/library/src/PeerConnectionController/AggregatedStats.ts @@ -10,6 +10,7 @@ import { DataChannelStats } from './DataChannelStats'; import { CandidateStat } from './CandidateStat'; import { CandidatePairStats } from './CandidatePairStats'; import { OutBoundRTPStats, OutBoundVideoStats } from './OutBoundRTPStats'; +import { SessionStats } from './SessionStats'; import { StreamStats } from './StreamStats'; import { CodecStats } from './CodecStats'; import { Logger } from '../Logger/Logger'; @@ -29,6 +30,7 @@ export class AggregatedStats { localCandidates: Array; remoteCandidates: Array; outBoundVideoStats: OutBoundVideoStats; + sessionStats: SessionStats; streamStats: StreamStats; codecs: Map; @@ -38,6 +40,7 @@ export class AggregatedStats { this.candidatePair = new CandidatePairStats(); this.DataChannelStats = new DataChannelStats(); this.outBoundVideoStats = new OutBoundVideoStats(); + this.sessionStats = new SessionStats(); this.streamStats = new StreamStats(); this.codecs = new Map(); } @@ -274,6 +277,28 @@ export class AggregatedStats { this.codecs.set(codecId, codecType); } + handleSessionStatistics( + videoStartTime: number, + inputController: boolean | null, + videoEncoderAvgQP: number + ) { + const deltaTime = Date.now() - videoStartTime; + this.sessionStats.runTime = new Date(deltaTime) + .toISOString() + .substr(11, 8) + .toString(); + + const controlsStreamInput = + inputController === null + ? 'Not sent yet' + : inputController + ? 'true' + : 'false'; + this.sessionStats.controlsStreamInput = controlsStreamInput; + + this.sessionStats.videoEncoderAvgQP = videoEncoderAvgQP; + } + /** * Check if a value coming in from our stats is actually a number * @param value - the number to be checked diff --git a/Frontend/library/src/PeerConnectionController/PeerConnectionController.ts b/Frontend/library/src/PeerConnectionController/PeerConnectionController.ts index b52230ce..1e1b414a 100644 --- a/Frontend/library/src/PeerConnectionController/PeerConnectionController.ts +++ b/Frontend/library/src/PeerConnectionController/PeerConnectionController.ts @@ -179,8 +179,13 @@ export class PeerConnectionController { this.onVideoStats(this.aggregatedStats); // Update the preferred codec selection based on what was actually negotiated - if(this.updateCodecSelection) { - this.config.setOptionSettingValue(OptionParameters.PreferredCodec, this.aggregatedStats.codecs.get(this.aggregatedStats.inboundVideoStats.codecId)) + if (this.updateCodecSelection) { + this.config.setOptionSettingValue( + OptionParameters.PreferredCodec, + this.aggregatedStats.codecs.get( + this.aggregatedStats.inboundVideoStats.codecId + ) + ); } }); } @@ -277,6 +282,7 @@ export class PeerConnectionController { 'ice connection state change: ' + state, 6 ); + this.onIceConnectionStateChange(state); } /** @@ -324,6 +330,15 @@ export class PeerConnectionController { // Default Functionality: Do Nothing } + /** + * An override method for onIceConnectionStateChange for use outside of the PeerConnectionController + * @param event - The webRtc iceconnectionstatechange event + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onIceConnectionStateChange(event: Event) { + // Default Functionality: Do Nothing + } + /** * An override method for onPeerIceCandidate for use outside of the PeerConnectionController * @param peerConnectionIceEvent - The peer ice candidate diff --git a/Frontend/library/src/PeerConnectionController/SessionStats.ts b/Frontend/library/src/PeerConnectionController/SessionStats.ts new file mode 100644 index 00000000..b5125ca6 --- /dev/null +++ b/Frontend/library/src/PeerConnectionController/SessionStats.ts @@ -0,0 +1,10 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +/** + * Session statistics + */ +export class SessionStats { + runTime: string; + controlsStreamInput: string; + videoEncoderAvgQP: number; +} diff --git a/Frontend/library/src/PixelStreaming/PixelStreaming.ts b/Frontend/library/src/PixelStreaming/PixelStreaming.ts new file mode 100644 index 00000000..eb3fb3c5 --- /dev/null +++ b/Frontend/library/src/PixelStreaming/PixelStreaming.ts @@ -0,0 +1,593 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import { Config, OptionParameters } from '../Config/Config'; +import { LatencyTestResults } from '../DataChannel/LatencyTestResults'; +import { AggregatedStats } from '../PeerConnectionController/AggregatedStats'; +import { WebRtcPlayerController } from '../WebRtcPlayer/WebRtcPlayerController'; +import { Flags, NumericParameters } from '../Config/Config'; +import { Logger } from '../Logger/Logger'; +import { + InitialSettings, + EncoderSettings, + WebRTCSettings +} from '../DataChannel/InitialSettings'; +import { OnScreenKeyboard } from '../UI/OnScreenKeyboard'; +import { + EventEmitter, + InitialSettingsEvent, + LatencyTestResultEvent, + PixelStreamingEvent, + StatsReceivedEvent, + StreamLoadingEvent, + VideoEncoderAvgQPEvent, + VideoInitializedEvent, + WebRtcAutoConnectEvent, + WebRtcConnectedEvent, + WebRtcConnectingEvent, + WebRtcDisconnectedEvent, + WebRtcFailedEvent, + WebRtcSdpEvent +} from '../Util/EventEmitter'; +import { MessageOnScreenKeyboard } from '../WebSockets/MessageReceive'; +import { WebXRController } from '../WebXR/WebXRController'; + +export interface PixelStreamingOverrides { + /** The DOM elment where Pixel Streaming video and user input event handlers are attached to. + * You can give an existing DOM element here. If not given, the library will create a new div element + * that is not attached anywhere. In this case you can later get access to this new element and + * attach it to your web page. */ + videoElementParent?: HTMLElement; +} + +/** + * The key class for the browser side of a Pixel Streaming application, it includes: + * WebRTC handling, XR support, input handling, and emitters for lifetime and state change events. + * Users are encouraged to use this class as is, through composition, or extend it. In any case, + * this will likely be the core of your Pixel Streaming experience in terms of functionality. + */ +export class PixelStreaming { + private webRtcController: WebRtcPlayerController; + private webXrController: WebXRController; + /** + * Configuration object. You can read or modify config through this object. Whenever + * the configuration is changed, the library will emit a `settingsChanged` event. + */ + public config: Config; + + private _videoElementParent: HTMLElement; + + _showActionOrErrorOnDisconnect = true; + + private onScreenKeyboardHelper: OnScreenKeyboard; + + private _videoStartTime: number; + private _inputController: boolean; + + private _eventEmitter: EventEmitter; + + /** + * @param config - A newly instantiated config object + * @param overrides - Parameters to override default behaviour + * returns the base Pixel streaming object + */ + constructor(config: Config, overrides?: PixelStreamingOverrides) { + this.config = config; + + if (overrides?.videoElementParent) { + this._videoElementParent = overrides.videoElementParent; + } + + this._eventEmitter = new EventEmitter(); + + this.configureSettings(); + + // setup WebRTC + this.setWebRtcPlayerController( + new WebRtcPlayerController(this.config, this) + ); + + // Onscreen keyboard + this.onScreenKeyboardHelper = new OnScreenKeyboard( + this.videoElementParent + ); + this.onScreenKeyboardHelper.unquantizeAndDenormalizeUnsigned = ( + x: number, + y: number + ) => + this.webRtcController.requestUnquantizedAndDenormalizeUnsigned( + x, + y + ); + this._activateOnScreenKeyboard = (command: MessageOnScreenKeyboard) => + this.onScreenKeyboardHelper.showOnScreenKeyboard(command); + + this.webXrController = new WebXRController(this.webRtcController); + } + + /** + * Gets the element that contains the video stream element. + */ + public get videoElementParent(): HTMLElement { + if (!this._videoElementParent) { + this._videoElementParent = document.createElement('div'); + this._videoElementParent.id = 'videoElementParent'; + } + return this._videoElementParent; + } + + /** + * Configure the settings with on change listeners and any additional per experience settings. + */ + private configureSettings(): void { + this.config._addOnSettingChangedListener( + Flags.IsQualityController, + (wantsQualityController: boolean) => { + // If the setting has been set to true (either programatically or the user has flicked the toggle) + // and we aren't currently quality controller, send the request + if ( + wantsQualityController === true && + !this.webRtcController.isQualityController + ) { + this.webRtcController.sendRequestQualityControlOwnership(); + } + } + ); + + this.config._addOnSettingChangedListener( + Flags.AFKDetection, + (isAFKEnabled: boolean) => { + this.webRtcController.setAfkEnabled(isAFKEnabled); + } + ); + + this.config._addOnSettingChangedListener( + Flags.MatchViewportResolution, + () => { + this.webRtcController.videoPlayer.updateVideoStreamSize(); + } + ); + + this.config._addOnSettingChangedListener( + Flags.HoveringMouseMode, + (isHoveringMouse: boolean) => { + this.config.setFlagLabel( + Flags.HoveringMouseMode, + `Control Scheme: ${ + isHoveringMouse ? 'Hovering' : 'Locked' + } Mouse` + ); + this.webRtcController.activateRegisterMouse(); + } + ); + + // encoder settings + this.config._addOnNumericSettingChangedListener( + NumericParameters.MinQP, + (newValue: number) => { + Logger.Log( + Logger.GetStackTrace(), + '-------- Sending MinQP --------', + 7 + ); + this.webRtcController.sendEncoderMinQP(newValue); + Logger.Log( + Logger.GetStackTrace(), + '-------------------------------------------', + 7 + ); + } + ); + + this.config._addOnNumericSettingChangedListener( + NumericParameters.MaxQP, + (newValue: number) => { + Logger.Log( + Logger.GetStackTrace(), + '-------- Sending encoder settings --------', + 7 + ); + this.webRtcController.sendEncoderMaxQP(newValue); + Logger.Log( + Logger.GetStackTrace(), + '-------------------------------------------', + 7 + ); + } + ); + + // WebRTC settings + this.config._addOnNumericSettingChangedListener( + NumericParameters.WebRTCMinBitrate, + (newValue: number) => { + Logger.Log( + Logger.GetStackTrace(), + '-------- Sending web rtc settings --------', + 7 + ); + this.webRtcController.sendWebRTCMinBitrate(newValue * 1000 /* kbps to bps */); + Logger.Log( + Logger.GetStackTrace(), + '-------------------------------------------', + 7 + ); + } + ); + + this.config._addOnNumericSettingChangedListener( + NumericParameters.WebRTCMaxBitrate, + (newValue: number) => { + Logger.Log( + Logger.GetStackTrace(), + '-------- Sending web rtc settings --------', + 7 + ); + this.webRtcController.sendWebRTCMaxBitrate(newValue * 1000 /* kbps to bps */); + Logger.Log( + Logger.GetStackTrace(), + '-------------------------------------------', + 7 + ); + } + ); + + this.config._addOnNumericSettingChangedListener( + NumericParameters.WebRTCFPS, + (newValue: number) => { + Logger.Log( + Logger.GetStackTrace(), + '-------- Sending web rtc settings --------', + 7 + ); + this.webRtcController.sendWebRTCFps(newValue); + Logger.Log( + Logger.GetStackTrace(), + '-------------------------------------------', + 7 + ); + } + ); + + this.config._addOnOptionSettingChangedListener( + OptionParameters.PreferredCodec, + (newValue: string) => { + if (this.webRtcController) { + this.webRtcController.setPreferredCodec(newValue); + } + } + ); + + this.config._registerOnChangeEvents(this._eventEmitter); + } + + /** + * Activate the on screen keyboard when receiving the command from the streamer + * @param command - the keyboard command + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _activateOnScreenKeyboard(command: MessageOnScreenKeyboard): void { + throw new Error('Method not implemented.'); + } + + /** + * Set the input control ownership + * @param inputControlOwnership - does the user have input control ownership + */ + _onInputControlOwnership(inputControlOwnership: boolean): void { + this._inputController = inputControlOwnership; + } + + /** + * Instantiate the WebRTCPlayerController interface to provide WebRTCPlayerController functionality within this class and set up anything that requires it + * @param webRtcPlayerController - a WebRtcPlayerController controller instance + */ + private setWebRtcPlayerController( + webRtcPlayerController: WebRtcPlayerController + ) { + this.webRtcController = webRtcPlayerController; + + this.webRtcController.setPreferredCodec( + this.config.getSettingOption(OptionParameters.PreferredCodec) + .selected + ); + this.webRtcController.resizePlayerStyle(); + + // connect if auto connect flag is enabled + this.checkForAutoConnect(); + } + + /** + * Connect to signaling server. + */ + public connect() { + this.webRtcController.connectToSignallingServer(); + } + + /** + * Reconnects to the signaling server. If connection is up, disconnects first + * before establishing a new connection + */ + public reconnect() { + this.webRtcController.restartStreamAutomatically(); + } + + /** + * Disconnect from the signaling server and close open peer connections. + */ + public disconnect() { + this.webRtcController.close(); + } + + /** + * Play the stream. Can be called only after a peer connection has been established. + */ + public play() { + this._onStreamLoading(); + this.webRtcController.playStream(); + } + + /** + * Auto connect if AutoConnect flag is enabled + */ + private checkForAutoConnect() { + // set up if the auto play will be used or regular click to start + if (this.config.isFlagEnabled(Flags.AutoConnect)) { + // if autoplaying show an info overlay while while waiting for the connection to begin + this._onWebRtcAutoConnect(); + this.webRtcController.connectToSignallingServer(); + } + } + + /** + * Emit an event on auto connecting + */ + _onWebRtcAutoConnect() { + this._eventEmitter.dispatchEvent(new WebRtcAutoConnectEvent()); + this._showActionOrErrorOnDisconnect = true; + } + + /** + * Set up functionality to happen when receiving a webRTC answer + */ + _onWebRtcSdp() { + this._eventEmitter.dispatchEvent(new WebRtcSdpEvent()); + } + + /** + * Emits a StreamLoading event + */ + _onStreamLoading() { + this._eventEmitter.dispatchEvent(new StreamLoadingEvent()); + } + + /** + * Event fired when the video is disconnected - emits given eventString or an override + * message from webRtcController if one has been set + * @param eventString - the event text that will be emitted + */ + _onDisconnect(eventString: string) { + // if we have overridden the default disconnection message, assign the new value here + if ( + this.webRtcController.getDisconnectMessageOverride() != '' && + this.webRtcController.getDisconnectMessageOverride() !== + undefined && + this.webRtcController.getDisconnectMessageOverride() != null + ) { + eventString = this.webRtcController.getDisconnectMessageOverride(); + this.webRtcController.setDisconnectMessageOverride(''); + } + + this._eventEmitter.dispatchEvent( + new WebRtcDisconnectedEvent({ + eventString, + showActionOrErrorOnDisconnect: + this._showActionOrErrorOnDisconnect + }) + ); + if (this._showActionOrErrorOnDisconnect == false) { + this._showActionOrErrorOnDisconnect = true; + } + } + + /** + * Handles when Web Rtc is connecting + */ + _onWebRtcConnecting() { + this._eventEmitter.dispatchEvent(new WebRtcConnectingEvent()); + } + + /** + * Handles when Web Rtc has connected + */ + _onWebRtcConnected() { + this._eventEmitter.dispatchEvent(new WebRtcConnectedEvent()); + } + + /** + * Handles when Web Rtc fails to connect + */ + _onWebRtcFailed() { + this._eventEmitter.dispatchEvent(new WebRtcFailedEvent()); + } + + /** + * Handle when the Video has been Initialized + */ + _onVideoInitialized() { + this._eventEmitter.dispatchEvent(new VideoInitializedEvent()); + this._videoStartTime = Date.now(); + } + + /** + * Set up functionality to happen when receiving latency test results + * @param latency - latency test results object + */ + _onLatencyTestResult(latencyTimings: LatencyTestResults) { + this._eventEmitter.dispatchEvent( + new LatencyTestResultEvent({ latencyTimings }) + ); + } + + /** + * Set up functionality to happen when receiving video statistics + * @param videoStats - video statistics as a aggregate stats object + */ + _onVideoStats(videoStats: AggregatedStats) { + // Duration + if (!this._videoStartTime || this._videoStartTime === undefined) { + this._videoStartTime = Date.now(); + } + videoStats.handleSessionStatistics( + this._videoStartTime, + this._inputController, + this.webRtcController.videoAvgQp + ); + + this._eventEmitter.dispatchEvent( + new StatsReceivedEvent({ aggregatedStats: videoStats }) + ); + } + + /** + * Set up functionality to happen when calculating the average video encoder qp + * @param QP - the quality number of the stream + */ + _onVideoEncoderAvgQP(QP: number) { + this._eventEmitter.dispatchEvent( + new VideoEncoderAvgQPEvent({ avgQP: QP }) + ); + } + + /** + * Set up functionality to happen when receiving and handling initial settings for the UE app + * @param settings - initial UE app settings + */ + _onInitialSettings(settings: InitialSettings) { + this._eventEmitter.dispatchEvent( + new InitialSettingsEvent({ settings }) + ); + if (settings.PixelStreamingSettings) { + const allowConsoleCommands = + settings.PixelStreamingSettings.AllowPixelStreamingCommands; + if (allowConsoleCommands === false) { + Logger.Info( + Logger.GetStackTrace(), + '-AllowPixelStreamingCommands=false, sending arbitrary console commands from browser to UE is disabled.' + ); + } + } + if (settings.EncoderSettings) { + this.config.setNumericSetting( + NumericParameters.MinQP, + settings.EncoderSettings.MinQP + ); + this.config.setNumericSetting( + NumericParameters.MaxQP, + settings.EncoderSettings.MaxQP + ); + } + if (settings.WebRTCSettings) { + this.config.setNumericSetting( + NumericParameters.WebRTCMinBitrate, + settings.WebRTCSettings.MinBitrate / 1000 /* bps to kbps */ + ); + this.config.setNumericSetting( + NumericParameters.WebRTCMaxBitrate, + settings.WebRTCSettings.MaxBitrate / 1000 /* bps to kbps */ + ); + this.config.setNumericSetting( + NumericParameters.WebRTCFPS, + settings.WebRTCSettings.FPS + ); + } + } + + /** + * Set up functionality to happen when setting quality control ownership of a stream + * @param hasQualityOwnership - does this user have quality ownership of the stream true / false + */ + _onQualityControlOwnership(hasQualityOwnership: boolean) { + this.config.setFlagEnabled( + Flags.IsQualityController, + hasQualityOwnership + ); + } + + /** + * Request a connection latency test. + * NOTE: There are plans to refactor all request* functions. Expect changes if you use this! + * @returns + */ + public requestLatencyTest() { + if (!this.webRtcController.videoPlayer.isVideoReady()) { + return false; + } + this.webRtcController.sendLatencyTest(); + return true; + } + + /** + * Request for the UE application to show FPS counter. + * NOTE: There are plans to refactor all request* functions. Expect changes if you use this! + * @returns + */ + public requestShowFps() { + if (!this.webRtcController.videoPlayer.isVideoReady()) { + return false; + } + this.webRtcController.sendShowFps(); + return true; + } + + /** + * Request for a new IFrame from the UE application. + * NOTE: There are plans to refactor all request* functions. Expect changes if you use this! + * @returns + */ + public requestIframe() { + if (!this.webRtcController.videoPlayer.isVideoReady()) { + return false; + } + this.webRtcController.sendIframeRequest(); + return true; + } + + /** + * Dispatch a new event. + * @param e event + * @returns + */ + public dispatchEvent(e: PixelStreamingEvent): boolean { + return this._eventEmitter.dispatchEvent(e); + } + + /** + * Register an event handler. + * @param type event name + * @param listener event handler function + */ + public addEventListener< + T extends PixelStreamingEvent['type'], + E extends PixelStreamingEvent & { type: T } + >(type: T, listener: (e: Event & E) => void) { + this._eventEmitter.addEventListener(type, listener); + } + + /** + * Remove an event handler. + * @param type event name + * @param listener event handler function + */ + public removeEventListener< + T extends PixelStreamingEvent['type'], + E extends PixelStreamingEvent & { type: T } + >(type: T, listener: (e: Event & E) => void) { + this._eventEmitter.removeEventListener(type, listener); + } + + /** + * Enable/disable XR mode. + */ + public toggleXR() { + this.webXrController.xrClicked(); + } +} diff --git a/Frontend/library/src/Util/EventEmitter.ts b/Frontend/library/src/Util/EventEmitter.ts new file mode 100644 index 00000000..c172ebdd --- /dev/null +++ b/Frontend/library/src/Util/EventEmitter.ts @@ -0,0 +1,465 @@ +import { + FlagsIds, + NumericParametersIds, + OptionParametersIds, + TextParametersIds +} from '../Config/Config'; +import { LatencyTestResults } from '../DataChannel/LatencyTestResults'; +import { AggregatedStats } from '../PeerConnectionController/AggregatedStats'; +import { InitialSettings } from '../pixelstreamingfrontend'; +import { MessageStreamerList } from '../WebSockets/MessageReceive'; +import { SettingFlag } from '../Config/SettingFlag'; +import { SettingNumber } from '../Config/SettingNumber'; +import { SettingText } from '../Config/SettingText'; +import { SettingOption } from '../Config/SettingOption'; + +/** + * An event that is emitted when AFK disconnect is about to happen. + * Can be cancelled by calling the callback function provided as part of the event. + */ +export class AfkWarningActivateEvent extends Event { + readonly type: 'afkWarningActivate'; + readonly data: { + /** How many seconds until the session is disconnected */ + countDown: number; + /** Callback function that needs to be called if you wish to cancel the AFK disconnect timeout. */ + dismissAfk: () => void; + }; + constructor(data: AfkWarningActivateEvent['data']) { + super('afkWarningActivate'); + this.data = data; + } +} + +/** + * An event that is emitted when the AFK disconnect countdown is updated. + */ +export class AfkWarningUpdateEvent extends Event { + readonly type: 'afkWarningUpdate'; + readonly data: { + /** How many seconds until the session is disconnected */ + countDown: number + }; + constructor(data: AfkWarningUpdateEvent['data']) { + super('afkWarningUpdate'); + this.data = data; + } +} + +/** + * An event that is emitted when AFK warning is deactivated. + */ +export class AfkWarningDeactivateEvent extends Event { + readonly type: 'afkWarningDeactivate'; + constructor() { + super('afkWarningDeactivate'); + } +} + +/** + * An event that is emitted when AFK countdown reaches 0 and the user is disconnected. + */ +export class AfkTimedOutEvent extends Event { + readonly type: 'afkTimedOut'; + constructor() { + super('afkTimedOut'); + } +} + +/** + * An event that is emitted when we receive new video quality value. + */ +export class VideoEncoderAvgQPEvent extends Event { + readonly type: 'videoEncoderAvgQP'; + readonly data: { + /** Average video quality value */ + avgQP: number + }; + constructor(data: VideoEncoderAvgQPEvent['data']) { + super('videoEncoderAvgQP'); + this.data = data; + } +} + +/** + * An event that is emitted after a WebRtc connection has been negotiated. + */ +export class WebRtcSdpEvent extends Event { + readonly type: 'webRtcSdp'; + constructor() { + super('webRtcSdp'); + } +} + +/** + * An event that is emitted when auto connecting. + */ +export class WebRtcAutoConnectEvent extends Event { + readonly type: 'webRtcAutoConnect'; + constructor() { + super('webRtcAutoConnect'); + } +} + +/** + * An event that is emitted when sending a WebRtc offer. + */ +export class WebRtcConnectingEvent extends Event { + readonly type: 'webRtcConnecting'; + constructor() { + super('webRtcConnecting'); + } +} + +/** + * An event that is emitted when WebRtc connection has been established. + */ +export class WebRtcConnectedEvent extends Event { + readonly type: 'webRtcConnected'; + constructor() { + super('webRtcConnected'); + } +} + +/** + * An event that is emitted if WebRtc connection has failed. + */ +export class WebRtcFailedEvent extends Event { + readonly type: 'webRtcFailed'; + constructor() { + super('webRtcFailed'); + } +} + +/** + * An event that is emitted if WebRtc connection is disconnected. + */ +export class WebRtcDisconnectedEvent extends Event { + readonly type: 'webRtcDisconnected'; + readonly data: { + /** Message describing the disconnect reason */ + eventString: string; + /** true if the user is able to reconnect, false if disconnected because of unrecoverable reasons like not able to connect to the signaling server */ + showActionOrErrorOnDisconnect: boolean; + }; + constructor(data: WebRtcDisconnectedEvent['data']) { + super('webRtcDisconnected'); + this.data = data; + } +} + +/** + * An event that is emitted when RTCDataChannel is opened. + */ +export class DataChannelOpenEvent extends Event { + readonly type: 'dataChannelOpen'; + readonly data: { + /** Data channel label. One of 'datachannel', 'send-datachannel', 'recv-datachannel' */ + label: string; + /** RTCDataChannel onOpen event */ + event: Event + }; + constructor(data: DataChannelOpenEvent['data']) { + super('dataChannelOpen'); + this.data = data; + } +} + +/** + * An event that is emitted when RTCDataChannel is closed. + */ +export class DataChannelCloseEvent extends Event { + readonly type: 'dataChannelClose'; + readonly data: { + /** Data channel label. One of 'datachannel', 'send-datachannel', 'recv-datachannel' */ + label: string; + /** RTCDataChannel onClose event */ + event: Event + }; + constructor(data: DataChannelCloseEvent['data']) { + super('dataChannelClose'); + this.data = data; + } +} + +/** + * An event that is emitted on RTCDataChannel errors. + */ +export class DataChannelErrorEvent extends Event { + readonly type: 'dataChannelError'; + readonly data: { + /** Data channel label. One of 'datachannel', 'send-datachannel', 'recv-datachannel' */ + label: string; + /** RTCDataChannel onError event */ + event: Event + }; + constructor(data: DataChannelErrorEvent['data']) { + super('dataChannelError'); + this.data = data; + } +} + +/** + * An event that is emitted when the video stream has been initialized. + */ +export class VideoInitializedEvent extends Event { + readonly type: 'videoInitialized'; + constructor() { + super('videoInitialized'); + } +} + +/** + * An event that is emitted when video stream loading starts. + */ +export class StreamLoadingEvent extends Event { + readonly type: 'streamLoading'; + constructor() { + super('streamLoading'); + } +} + +/** + * An event that is emitted if there are errors loading the video stream. + */ +export class PlayStreamErrorEvent extends Event { + readonly type: 'playStreamError'; + readonly data: { + /** Error message */ + message: string + }; + constructor(data: PlayStreamErrorEvent['data']) { + super('playStreamError'); + this.data = data; + } +} + +/** + * An event that is emitted before trying to start video playback. + */ +export class PlayStreamEvent extends Event { + readonly type: 'playStream'; + constructor() { + super('playStream'); + } +} + +/** + * An event that is emitted if the browser rejects video playback. Can happen for example if + * video auto-play without user interaction is refused by the browser. + */ +export class PlayStreamRejectedEvent extends Event { + readonly type: 'playStreamRejected'; + readonly data: { + /** Rejection reason */ + reason: unknown + }; + constructor(data: PlayStreamRejectedEvent['data']) { + super('playStreamRejected'); + this.data = data; + } +} + +/** + * An event that is emitted when receiving a full FreezeFrame image from UE. + */ +export class LoadFreezeFrameEvent extends Event { + readonly type: 'loadFreezeFrame'; + readonly data: { + /** true if should show click-to-play overlay, not the freeze frame contents */ + shouldShowPlayOverlay: boolean; + /** true if the received image is valid */ + isValid: boolean; + /** Image data. Can be e.g. displayed by encoding as a data url. */ + jpegData?: Uint8Array; + }; + constructor(data: LoadFreezeFrameEvent['data']) { + super('loadFreezeFrame'); + this.data = data; + } +} + +/** + * An event that is emitted when receiving UnfreezeFrame message from UE and video playback is about to be resumed. + */ +export class HideFreezeFrameEvent extends Event { + readonly type: 'hideFreezeFrame'; + constructor() { + super('hideFreezeFrame'); + } +} + +/** + * An event that is emitted when receiving WebRTC statistics. + */ +export class StatsReceivedEvent extends Event { + readonly type: 'statsReceived'; + readonly data: { + /** Statistics object */ + aggregatedStats: AggregatedStats + }; + constructor(data: StatsReceivedEvent['data']) { + super('statsReceived'); + this.data = data; + } +} + +/** + * An event that is emitted when streamer list changes. + */ +export class StreamerListMessageEvent extends Event { + readonly type: 'streamerListMessage'; + readonly data: { + /** Streamer list message containing an array of streamer ids */ + messageStreamerList: MessageStreamerList; + /** Auto-selected streamer from the list, or null if unable to auto-select and user should be prompted to select */ + autoSelectedStreamerId: string | null; + }; + constructor(data: StreamerListMessageEvent['data']) { + super('streamerListMessage'); + this.data = data; + } +} + +/** + * An event that is emitted when receiving latency test results. + */ +export class LatencyTestResultEvent extends Event { + readonly type: 'latencyTestResult'; + readonly data: { + /** Latency test result object */ + latencyTimings: LatencyTestResults + }; + constructor(data: LatencyTestResultEvent['data']) { + super('latencyTestResult'); + this.data = data; + } +} + +/** + * An event that is emitted when receiving initial settings from UE. + */ +export class InitialSettingsEvent extends Event { + readonly type: 'initialSettings'; + readonly data: { + /** Initial settings from UE */ + settings: InitialSettings + }; + constructor(data: InitialSettingsEvent['data']) { + super('initialSettings'); + this.data = data; + } +} + +export type SettingsData = + | { + /** Flag id */ + id: FlagsIds; + type: 'flag'; + /** Flag value */ + value: boolean; + /** SettingFlag object */ + target: SettingFlag; + } + | { + /** Numeric setting id */ + id: NumericParametersIds; + type: 'number'; + /** Numeric setting value */ + value: number; + /** SettingNumber object */ + target: SettingNumber; + } + | { + /** Text setting id */ + id: TextParametersIds; + type: 'text'; + /** Text setting value */ + value: string; + /** SettingText object */ + target: SettingText; + } + | { + /** Option setting id */ + id: OptionParametersIds; + type: 'option'; + /** Option setting selected value */ + value: string; + /** SettingOption object */ + target: SettingOption; + }; + +/** + * An event that is emitted when PixelStreaming settings change. + */ +export class SettingsChangedEvent extends Event { + readonly type: 'settingsChanged'; + readonly data: SettingsData; + constructor(data: SettingsChangedEvent['data']) { + super('settingsChanged'); + this.data = data; + } +} + +export type PixelStreamingEvent = + | AfkWarningActivateEvent + | AfkWarningUpdateEvent + | AfkWarningDeactivateEvent + | AfkTimedOutEvent + | VideoEncoderAvgQPEvent + | WebRtcSdpEvent + | WebRtcAutoConnectEvent + | WebRtcConnectingEvent + | WebRtcConnectedEvent + | WebRtcFailedEvent + | WebRtcDisconnectedEvent + | DataChannelOpenEvent + | DataChannelCloseEvent + | DataChannelErrorEvent + | VideoInitializedEvent + | StreamLoadingEvent + | PlayStreamErrorEvent + | PlayStreamEvent + | PlayStreamRejectedEvent + | LoadFreezeFrameEvent + | HideFreezeFrameEvent + | StatsReceivedEvent + | StreamerListMessageEvent + | LatencyTestResultEvent + | InitialSettingsEvent + | SettingsChangedEvent; + +export class EventEmitter extends EventTarget { + /** + * Dispatch a new event. + * @param e event + * @returns + */ + public dispatchEvent(e: PixelStreamingEvent): boolean { + return super.dispatchEvent(e); + } + + /** + * Register an event handler. + * @param type event name + * @param listener event handler function + */ + public addEventListener< + T extends PixelStreamingEvent['type'], + E extends PixelStreamingEvent & { type: T } + >(type: T, listener: (e: Event & E) => void) { + super.addEventListener(type, listener); + } + + /** + * Remove an event handler. + * @param type event name + * @param listener event handler function + */ + public removeEventListener< + T extends PixelStreamingEvent['type'], + E extends PixelStreamingEvent & { type: T } + >(type: T, listener: (e: Event & E) => void) { + super.removeEventListener(type, listener); + } +} diff --git a/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts b/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts index c4cc402c..375603f2 100644 --- a/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts +++ b/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts @@ -48,9 +48,19 @@ import { CoordinateConverter, UnquantizedDenormalizedUnsignedCoord } from '../Util/CoordinateConverter'; -import { Application } from '../Application/Application'; +import { PixelStreaming } from '../PixelStreaming/PixelStreaming'; import { ITouchController } from '../Inputs/ITouchController'; -import { AFKOverlay } from '../AFK/AFKOverlay'; +import { + DataChannelCloseEvent, + DataChannelErrorEvent, + DataChannelOpenEvent, + HideFreezeFrameEvent, + LoadFreezeFrameEvent, + PlayStreamErrorEvent, + PlayStreamEvent, + PlayStreamRejectedEvent, + StreamerListMessageEvent +} from '../Util/EventEmitter'; /** * Entry point for the WebRTC Player */ @@ -74,7 +84,7 @@ export class WebRtcPlayerController { afkController: AFKController; videoElementParentClientRect: DOMRect; latencyStartTime: number; - application: Application; + pixelStreaming: PixelStreaming; streamMessageController: StreamMessageController; sendDescriptorController: SendDescriptorController; sendMessageController: SendMessageController; @@ -89,7 +99,8 @@ export class WebRtcPlayerController { statsTimerHandle: number; file: FileTemplate; preferredCodec: string; - peerConfig: RTCConfiguration + peerConfig: RTCConfiguration; + videoAvgQp: number; // if you override the disconnection message by calling the interface method setDisconnectMessageOverride // it will use this property to store the override message string @@ -98,11 +109,11 @@ export class WebRtcPlayerController { /** * * @param config - the frontend config object - * @param application - the application object + * @param pixelStreaming - the PixelStreaming object */ - constructor(config: Config, application: Application) { + constructor(config: Config, pixelStreaming: PixelStreaming) { this.config = config; - this.application = application; + this.pixelStreaming = pixelStreaming; this.responseController = new ResponseController(); this.file = new FileTemplate(); @@ -112,9 +123,11 @@ export class WebRtcPlayerController { }; // set up the afk logic class and connect up its method for closing the signaling server - const afkOverlay = new AFKOverlay(this.application.videoElementParent); - afkOverlay.onAction(() => this.onAfkTriggered()); - this.afkController = new AFKController(this.config, afkOverlay); + this.afkController = new AFKController( + this.config, + this.pixelStreaming, + this.onAfkTriggered.bind(this) + ); this.afkController.onAFKTimedOutCallback = () => { this.setDisconnectMessageOverride( 'You have been disconnected due to inactivity' @@ -123,11 +136,11 @@ export class WebRtcPlayerController { }; this.freezeFrameController = new FreezeFrameController( - this.application.videoElementParent + this.pixelStreaming.videoElementParent ); this.videoPlayer = new VideoPlayer( - this.application.videoElementParent, + this.pixelStreaming.videoElementParent, this.config ); this.videoPlayer.onVideoInitialized = () => @@ -157,6 +170,10 @@ export class WebRtcPlayerController { this.sendrecvDataChannelController = new DataChannelController(); this.recvDataChannelController = new DataChannelController(); + this.registerDataChannelEventEmitters( + this.sendrecvDataChannelController + ); + this.registerDataChannelEventEmitters(this.recvDataChannelController); this.dataChannelSender = new DataChannelSender( this.sendrecvDataChannelController ); @@ -173,12 +190,14 @@ export class WebRtcPlayerController { this.webSocketController.onStreamerList = ( messageList: MessageReceive.MessageStreamerList ) => this.handleStreamerListMessage(messageList); - this.webSocketController.onWebSocketOncloseOverlayMessage = (event) => - this.application.onDisconnect( + this.webSocketController.onWebSocketOncloseOverlayMessage = (event) => { + this.pixelStreaming._onDisconnect( `Websocket disconnect (${event.code}) ${ event.reason != '' ? '- ' + event.reason : '' }` ); + this.setVideoEncoderAvgQP(0); + }; this.webSocketController.onOpen.addEventListener('open', () => { this.webSocketController.requestStreamerList(); }); @@ -206,12 +225,6 @@ export class WebRtcPlayerController { this.registerMessageHandlers(); this.streamMessageController.populateDefaultProtocol(); - // now that the application has finished instantiating connect the rest of the afk methods to the afk logic class - this.afkController.showAfkOverlay = () => - this.application.showAfkOverlay(this.afkController.countDown); - this.afkController.hideCurrentOverlay = () => - this.application.hideCurrentOverlay(); - this.inputClassesFactory = new InputClassesFactory( this.streamMessageController, this.videoPlayer, @@ -222,13 +235,20 @@ export class WebRtcPlayerController { this.isQualityController = false; this.preferredCodec = ''; - this.config.addOnOptionSettingChangedListener(OptionParameters.StreamerId, (streamerid) => { + this.config._addOnOptionSettingChangedListener( + OptionParameters.StreamerId, + (streamerid) => { // close the current peer connection and create a new one this.peerConnectionController.peerConnection.close(); - this.peerConnectionController.createPeerConnection(this.peerConfig, this.preferredCodec); + this.peerConnectionController.createPeerConnection( + this.peerConfig, + this.preferredCodec + ); this.webSocketController.sendSubscribe(streamerid); } ); + + this.setVideoEncoderAvgQP(-1); } /** @@ -650,7 +670,7 @@ export class WebRtcPlayerController { ); const command: MessageOnScreenKeyboard = JSON.parse(commandAsString); if (command.command === 'onScreenKeyboard') { - this.application.activateOnScreenKeyboard(command); + this.pixelStreaming._activateOnScreenKeyboard(command); } } @@ -700,7 +720,11 @@ export class WebRtcPlayerController { Logger.Error( Logger.GetStackTrace(), `ToStreamer->${messageType} protocol definition was malformed as it didn't contain at least an id and a byteLength\n - Definition was: ${JSON.stringify(message, null, 2)}` + Definition was: ${JSON.stringify( + message, + null, + 2 + )}` ); // return in a forEach is equivalent to a continue in a normal for loop return; @@ -800,7 +824,7 @@ export class WebRtcPlayerController { Logger.GetStackTrace(), `Received input controller message - will your input control the stream: ${inputControlOwnership}` ); - this.application.onInputControlOwnership(inputControlOwnership); + this.pixelStreaming._onInputControlOwnership(inputControlOwnership); } onAfkTriggered(): void { @@ -838,16 +862,19 @@ export class WebRtcPlayerController { } // if a websocket object has not been created connect normally without closing - if (!this.webSocketController.webSocket) { + if ( + !this.webSocketController.webSocket || + this.webSocketController.webSocket.readyState === WebSocket.CLOSED + ) { Logger.Log( Logger.GetStackTrace(), 'A websocket connection has not been made yet so we will start the stream' ); - this.application.onWebRtcAutoConnect(); + this.pixelStreaming._onWebRtcAutoConnect(); this.connectToSignallingServer(); } else { // set the replay status so we get a text overlay over an action overlay - this.application.showActionOrErrorOnDisconnect = false; + this.pixelStreaming._showActionOrErrorOnDisconnect = false; // set the disconnect message this.setDisconnectMessageOverride('Restarting stream...'); @@ -857,7 +884,7 @@ export class WebRtcPlayerController { // wait for the connection to close and restart the connection const autoConnectTimeout = setTimeout(() => { - this.application.onWebRtcAutoConnect(); + this.pixelStreaming._onWebRtcAutoConnect(); this.connectToSignallingServer(); clearTimeout(autoConnectTimeout); }, 3000); @@ -868,9 +895,15 @@ export class WebRtcPlayerController { * Loads a freeze frame if it is required otherwise shows the play overlay */ loadFreezeFrameOrShowPlayOverlay() { + this.pixelStreaming.dispatchEvent( + new LoadFreezeFrameEvent({ + shouldShowPlayOverlay: this.shouldShowPlayOverlay, + isValid: this.freezeFrameController.valid, + jpegData: this.freezeFrameController.jpeg + }) + ); if (this.shouldShowPlayOverlay === true) { Logger.Log(Logger.GetStackTrace(), 'showing play overlay'); - this.application.showPlayOverlay(); this.resizePlayerStyle(); } else { Logger.Log(Logger.GetStackTrace(), 'showing freeze frame'); @@ -907,6 +940,9 @@ export class WebRtcPlayerController { 6 ); setTimeout(() => { + this.pixelStreaming.dispatchEvent( + new HideFreezeFrameEvent() + ); this.freezeFrameController.hideFreezeFrame(); }, this.freezeFrameController.freezeFrameDelay); if (this.videoPlayer.getVideoElement()) { @@ -946,13 +982,12 @@ export class WebRtcPlayerController { */ playStream() { if (!this.videoPlayer.getVideoElement()) { - this.application.showErrorOverlay( - 'Could not play video stream because the video player was not initialized correctly.' - ); - Logger.Error( - Logger.GetStackTrace(), - 'Could not player video stream because the video player was not initialized correctly.' + const message = + 'Could not play video stream because the video player was not initialized correctly.'; + this.pixelStreaming.dispatchEvent( + new PlayStreamErrorEvent({ message }) ); + Logger.Error(Logger.GetStackTrace(), message); // set the disconnect message this.setDisconnectMessageOverride( @@ -976,7 +1011,7 @@ export class WebRtcPlayerController { this.config.isFlagEnabled(Flags.FakeMouseWithTouches), this.videoElementParentClientRect ); - this.application.hideCurrentOverlay(); + this.pixelStreaming.dispatchEvent(new PlayStreamEvent()); if (this.streamController.audioElement.srcObject) { this.streamController.audioElement.muted = @@ -993,7 +1028,11 @@ export class WebRtcPlayerController { Logger.GetStackTrace(), 'Browser does not support autoplaying video without interaction - to resolve this we are going to show the play button overlay.' ); - this.application.showPlayOverlay(); + this.pixelStreaming.dispatchEvent( + new PlayStreamRejectedEvent({ + reason: onRejectedReason + }) + ); }); } else { this.playVideo(); @@ -1017,7 +1056,9 @@ export class WebRtcPlayerController { Logger.GetStackTrace(), 'Browser does not support autoplaying video without interaction - to resolve this we are going to show the play button overlay.' ); - this.application.showPlayOverlay(); + this.pixelStreaming.dispatchEvent( + new PlayStreamRejectedEvent({ reason: onRejectedReason }) + ); }); } @@ -1028,8 +1069,6 @@ export class WebRtcPlayerController { if (this.config.isFlagEnabled(Flags.AutoPlayVideo)) { // attempt to play the video this.playStream(); - } else { - this.application.showPlayOverlay(); } this.resizePlayerStyle(); } @@ -1119,9 +1158,20 @@ export class WebRtcPlayerController { // set up webRtc text overlays this.peerConnectionController.showTextOverlayConnecting = () => - this.application.onWebRtcConnecting(); + this.pixelStreaming._onWebRtcConnecting(); this.peerConnectionController.showTextOverlaySetupFailure = () => - this.application.onWebRtcFailed(); + this.pixelStreaming._onWebRtcFailed(); + let webRtcConnectedSent = false; + this.peerConnectionController.onIceConnectionStateChange = () => { + // Browsers emit "connected" when getting first connection and "completed" when finishing + // candidate checking. However, sometimes browsers can skip "connected" and only emit "completed". + // Therefore need to check both cases and emit onWebRtcConnected only once on the first hit. + if (!webRtcConnectedSent && + ["connected", "completed"].includes(this.peerConnectionController.peerConnection.iceConnectionState)) { + this.pixelStreaming._onWebRtcConnected(); + webRtcConnectedSent = true; + } + }; /* RTC Peer Connection on Track event -> handle on track */ this.peerConnectionController.onTrack = (trackEvent: RTCTrackEvent) => @@ -1212,23 +1262,45 @@ export class WebRtcPlayerController { 6 ); - const settingOptions = [...messageStreamerList.ids] // copy the original messageStreamerList.ids - settingOptions.unshift('') // add an empty option at the top - this.config.setOptionSettingOptions(OptionParameters.StreamerId, settingOptions); + const settingOptions = [...messageStreamerList.ids]; // copy the original messageStreamerList.ids + settingOptions.unshift(''); // add an empty option at the top + this.config.setOptionSettingOptions( + OptionParameters.StreamerId, + settingOptions + ); const urlParams = new URLSearchParams(window.location.search); - if(messageStreamerList.ids.length == 1) { + let autoSelectedStreamerId: string | null = null; + if (messageStreamerList.ids.length == 1) { // If there's only a single streamer, subscribe to it regardless of what is in the URL - this.config.setOptionSettingValue(OptionParameters.StreamerId, messageStreamerList.ids[0]); - } else if (this.config.isFlagEnabled(Flags.PreferSFU) && messageStreamerList.ids.includes("SFU")) { + autoSelectedStreamerId = messageStreamerList.ids[0]; + } else if ( + this.config.isFlagEnabled(Flags.PreferSFU) && + messageStreamerList.ids.includes('SFU') + ) { // If the SFU toggle is on and there's an SFU connected, subscribe to it regardless of what is in the URL - this.config.setOptionSettingValue(OptionParameters.StreamerId, "SFU"); - } else if (urlParams.has(OptionParameters.StreamerId) && messageStreamerList.ids.includes(urlParams.get(OptionParameters.StreamerId))) { + autoSelectedStreamerId = 'SFU'; + } else if ( + urlParams.has(OptionParameters.StreamerId) && + messageStreamerList.ids.includes( + urlParams.get(OptionParameters.StreamerId) + ) + ) { // If there's a streamer ID in the URL and a streamer with this ID is connected, set it as the selected streamer - this.config.setOptionSettingValue(OptionParameters.StreamerId, urlParams.get(OptionParameters.StreamerId)); - } else { - this.application.showTextOverlay('Multiple streamers detected. Use the dropdown in the settings menu to select the streamer.'); + autoSelectedStreamerId = urlParams.get(OptionParameters.StreamerId); + } + if (autoSelectedStreamerId !== null) { + this.config.setOptionSettingValue( + OptionParameters.StreamerId, + autoSelectedStreamerId + ); } + this.pixelStreaming.dispatchEvent( + new StreamerListMessageEvent({ + messageStreamerList, + autoSelectedStreamerId + }) + ); } /** @@ -1321,7 +1393,7 @@ export class WebRtcPlayerController { // start the afk warning timer as PS is now running this.afkController.startAfkWarningTimer(); // show the overlay that we have negotiated a connection - this.application.onWebRtcSdp(); + this.pixelStreaming._onWebRtcSdp(); if (this.statsTimerHandle && this.statsTimerHandle !== undefined) { window.clearInterval(this.statsTimerHandle); @@ -1442,6 +1514,21 @@ export class WebRtcPlayerController { this.webSocketController.close(); } + /** + * Close the peer connection + */ + closePeerConnection() { + this.peerConnectionController.close(); + } + + /** + * Close all connections + */ + close() { + this.closeSignalingServer(); + this.closePeerConnection(); + } + /** * Fires a Video Stats Event in the RTC Peer Connection */ @@ -1460,64 +1547,87 @@ export class WebRtcPlayerController { } /** - * Send the Encoder Settings to the UE Instance as a UE UI Descriptor. - * @param encoder - Encoder Settings + * Send the MinQP encoder setting to the UE Instance. + * @param minQP - The lower bound for QP when encoding + * valid values are (1-51) where: + * 1 = Best quality but highest bitrate. + * 51 = Worst quality but lowest bitrate. + * By default the minQP is 1 meaning the encoder is free + * to aim for the best quality it can on the given network link. */ - sendEncoderSettings(encoder: EncoderSettings) { - Logger.Log( - Logger.GetStackTrace(), - '---- Encoder Settings ----\n' + - JSON.stringify(encoder, undefined, 4) + - '\n-------------------------------', - 6 - ); + sendEncoderMinQP(minQP: number) { + Logger.Log(Logger.GetStackTrace(), `MinQP=${minQP}\n`, 6); - if (encoder.MinQP != null) { + if (minQP != null) { this.sendDescriptorController.emitCommand({ - 'Encoder.MinQP': encoder.MinQP - }); - } - if (encoder.MaxQP != null) { - this.sendDescriptorController.emitCommand({ - 'Encoder.MaxQP': encoder.MaxQP + 'Encoder.MinQP': minQP }); } } /** - * Send the WebRTC Settings to the UE Instance as a UE UI Descriptor. - * @param webRTC - Web RTC Settings + * Send the MaxQP encoder setting to the UE Instance. + * @param maxQP - The upper bound for QP when encoding + * valid values are (1-51) where: + * 1 = Best quality but highest bitrate. + * 51 = Worst quality but lowest bitrate. + * By default the maxQP is 51 meaning the encoder is free + * to drop quality as low as needed on the given network link. */ - sendWebRtcSettings(webRTC: WebRTCSettings) { - Logger.Log( - Logger.GetStackTrace(), - '---- WebRTC Settings ----\n' + - JSON.stringify(webRTC, undefined, 4) + - '\n-------------------------------', - 6 - ); + sendEncoderMaxQP(maxQP: number) { + Logger.Log(Logger.GetStackTrace(), `MaxQP=${maxQP}\n`, 6); - // 4.27 and 5 compatibility - if (webRTC.FPS != null) { - this.sendDescriptorController.emitCommand({ - 'WebRTC.Fps': webRTC.FPS - }); + if (maxQP != null) { this.sendDescriptorController.emitCommand({ - 'WebRTC.MaxFps': webRTC.FPS + 'Encoder.MaxQP': maxQP }); } - if (webRTC.MinBitrate != null) { + } + + /** + * Send the { WebRTC.MinBitrate: SomeNumber }} command to UE to set + * the minimum bitrate that we allow WebRTC to use + * (note setting this too high in poor networks can be problematic). + * @param minBitrate - The minimum bitrate we would like WebRTC to not fall below. + */ + sendWebRTCMinBitrate(minBitrate: number) { + Logger.Log(Logger.GetStackTrace(), `WebRTC Min Bitrate=${minBitrate}`, 6); + if (minBitrate != null) { this.sendDescriptorController.emitCommand({ - 'PixelStreaming.WebRTC.MinBitrate': webRTC.MinBitrate + 'WebRTC.MinBitrate': minBitrate }); } - if (webRTC.MaxBitrate != null) { + } + + /** + * Send the { WebRTC.MaxBitrate: SomeNumber }} command to UE to set + * the minimum bitrate that we allow WebRTC to use + * (note setting this too low could result in blocky video). + * @param minBitrate - The minimum bitrate we would like WebRTC to not fall below. + */ + sendWebRTCMaxBitrate(maxBitrate: number) { + Logger.Log(Logger.GetStackTrace(), `WebRTC Max Bitrate=${maxBitrate}`, 6); + if (maxBitrate != null) { this.sendDescriptorController.emitCommand({ - 'PixelStreaming.WebRTC.MaxBitrate ': webRTC.MaxBitrate + 'WebRTC.MaxBitrate': maxBitrate }); } } + /** + * Send the { WebRTC.Fps: SomeNumber }} UE 5.0+ + * and { WebRTC.MaxFps } UE 4.27 command to set + * the maximum fps we would like WebRTC to stream at. + * @param fps - The maximum stream fps. + */ + sendWebRTCFps(fps: number) { + Logger.Log(Logger.GetStackTrace(), `WebRTC FPS=${fps}`, 6); + if (fps != null) { + this.sendDescriptorController.emitCommand({'WebRTC.Fps': fps}); + this.sendDescriptorController.emitCommand({'WebRTC.MaxFps': fps}); /* TODO: Remove when UE 4.27 unsupported. */ + } + } + /** * Sends the UI Descriptor `stat fps` to the UE Instance */ @@ -1596,7 +1706,7 @@ export class WebRtcPlayerController { latencyTestResults.networkLatency, +latencyTestResults.CaptureToSendMs); } - this.application.onLatencyTestResult(latencyTestResults); + this.pixelStreaming._onLatencyTestResult(latencyTestResults); } /** @@ -1639,7 +1749,7 @@ export class WebRtcPlayerController { initialSettings.ueCompatible(); Logger.Log(Logger.GetStackTrace(), payloadAsString, 6); - this.application.onInitialSettings(initialSettings); + this.pixelStreaming._onInitialSettings(initialSettings); } /** @@ -1655,14 +1765,14 @@ export class WebRtcPlayerController { const AvgQP = Number( new TextDecoder('utf-16').decode(message.slice(1)) ); - this.application.onVideoEncoderAvgQP(AvgQP); + this.setVideoEncoderAvgQP(AvgQP); } /** * Handles when the video element has been loaded with a srcObject */ handleVideoInitialized() { - this.application.onVideoInitialized(); + this.pixelStreaming._onVideoInitialized(); // either autoplay the video or set up the play overlay this.autoPlayVideoOrSetUpPlayOverlay(); @@ -1686,7 +1796,9 @@ export class WebRtcPlayerController { Logger.GetStackTrace(), `Received quality controller message, will control quality: ${this.isQualityController}` ); - this.application.onQualityControlOwnership(this.isQualityController); + this.pixelStreaming._onQualityControlOwnership( + this.isQualityController + ); } /** @@ -1694,7 +1806,7 @@ export class WebRtcPlayerController { * @param stats - Aggregated Stats */ handleVideoStats(stats: AggregatedStats) { - this.application.onVideoStats(stats); + this.pixelStreaming._onVideoStats(stats); } /** @@ -1725,4 +1837,24 @@ export class WebRtcPlayerController { this.peerConnectionController.updateCodecSelection = false; } } + + setVideoEncoderAvgQP(avgQP: number) { + this.videoAvgQp = avgQP; + this.pixelStreaming._onVideoEncoderAvgQP(this.videoAvgQp); + } + + registerDataChannelEventEmitters(dataChannel: DataChannelController) { + dataChannel.onOpen = (label, event) => + this.pixelStreaming.dispatchEvent( + new DataChannelOpenEvent({ label, event }) + ); + dataChannel.onClose = (label, event) => + this.pixelStreaming.dispatchEvent( + new DataChannelCloseEvent({ label, event }) + ); + dataChannel.onError = (label, event) => + this.pixelStreaming.dispatchEvent( + new DataChannelErrorEvent({ label, event }) + ); + } } diff --git a/Frontend/library/src/WebXR/WebXRController.ts b/Frontend/library/src/WebXR/WebXRController.ts index 082f2834..955f7af2 100644 --- a/Frontend/library/src/WebXR/WebXRController.ts +++ b/Frontend/library/src/WebXR/WebXRController.ts @@ -289,10 +289,12 @@ export class WebXRController { } static isSessionSupported(mode: XRSessionMode): Promise { - if(navigator.xr) { + if (navigator.xr) { return navigator.xr.isSessionSupported(mode); } else { - return new Promise(() => { return false; }) + return new Promise(() => { + return false; + }); } } } diff --git a/Frontend/library/src/pixelstreamingfrontend.ts b/Frontend/library/src/pixelstreamingfrontend.ts index 0a391603..d4dcca7e 100644 --- a/Frontend/library/src/pixelstreamingfrontend.ts +++ b/Frontend/library/src/pixelstreamingfrontend.ts @@ -7,15 +7,20 @@ export { ControlSchemeType, Flags, NumericParameters, - TextParameters + TextParameters, + OptionParameters, + FlagsIds, + NumericParametersIds, + TextParametersIds, + OptionParametersIds, + AllSettings } from './Config/Config'; export { SettingBase } from './Config/SettingBase'; export { SettingFlag } from './Config/SettingFlag'; -export { SettingsPanel } from './UI/SettingsPanel'; -export { StatsPanel } from './UI/StatsPanel'; export { SettingNumber } from './Config/SettingNumber'; -export { LabelledButton } from './UI/LabelledButton'; -export { Application } from './Application/Application'; +export { SettingOption } from './Config/SettingOption'; +export { SettingText } from './Config/SettingText'; +export { PixelStreaming } from './PixelStreaming/PixelStreaming'; export { AFKController as AfkLogic } from './AFK/AFKController'; @@ -29,20 +34,10 @@ export { AggregatedStats } from './PeerConnectionController/AggregatedStats'; export { Logger } from './Logger/Logger'; export { UnquantizedDenormalizedUnsignedCoord as UnquantizedAndDenormalizeUnsigned } from './Util/CoordinateConverter'; export { MessageSend } from './WebSockets/MessageSend'; -export { MessageRecv } from './WebSockets/MessageReceive'; +export { MessageRecv, MessageStreamerList } from './WebSockets/MessageReceive'; export { WebSocketController } from './WebSockets/WebSocketController'; export { SignallingProtocol } from './WebSockets/SignallingProtocol'; -export { AFKOverlay } from './AFK/AFKOverlay'; -export { ActionOverlay } from './Overlay/ActionOverlay'; -export { OverlayBase } from './Overlay/BaseOverlay'; -export { ConnectOverlay } from './Overlay/ConnectOverlay'; -export { DisconnectOverlay } from './Overlay/DisconnectOverlay'; -export { ErrorOverlay } from './Overlay/ErrorOverlay'; -export { InfoOverlay } from './Overlay/InfoOverlay'; -export { PlayOverlay } from './Overlay/PlayOverlay'; -export { TextOverlay } from './Overlay/TextOverlay'; - export { CandidatePairStats } from './PeerConnectionController/CandidatePairStats'; export { CandidateStat } from './PeerConnectionController/CandidateStat'; export { DataChannelStats } from './PeerConnectionController/DataChannelStats'; @@ -51,8 +46,4 @@ export { InboundVideoStats } from './PeerConnectionController/InboundRTPStats'; export { OutBoundVideoStats } from './PeerConnectionController/OutBoundRTPStats'; -export { PixelStreamingApplicationStyle } from './Application/PixelStreamingApplicationStyles'; - -import { PixelStreamingApplicationStyle } from './Application/PixelStreamingApplicationStyles'; -export const PixelStreamingApplicationStyles = - new PixelStreamingApplicationStyle(); +export * from './Util/EventEmitter'; diff --git a/Frontend/ui-library/.cspell.json b/Frontend/ui-library/.cspell.json new file mode 100644 index 00000000..58b170aa --- /dev/null +++ b/Frontend/ui-library/.cspell.json @@ -0,0 +1,48 @@ +{ + "version": "0.1", + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json", + "language": "en", + "words": [ + "unquantize", + "denormalize", + "noselect", + "rtcfps", + "autoplaying", + "kbps", + "webrtc", + "textbox", + "unquantized", + "denormalized", + "gamepadconnected", + "gamepaddisconnected", + "webkitgamepadconnected", + "webkitgamepaddisconnected", + "UE's", + "mozpointerlockchange", + "backquote", + "munge", + "useinbandfec", + "sprop", + "maxcapturerate", + "maxaveragebitrate", + "peerconnection", + "charcode", + "numtouches", + "ctrler", + "mozfullscreenchange", + "tooltiptext", + "wifi", + "bytelength", + "datachannels", + "DATACHANNELREQUEST", + "SFURECVDATACHANNELREADY", + "onmessagebinary" + ], + "flagWords": [], + "ignorePaths": [ + "package.json", + "package-lock.json", + "tsconfig.json", + "node_modules/**" + ] +} diff --git a/Frontend/ui-library/.eslintignore b/Frontend/ui-library/.eslintignore new file mode 100644 index 00000000..ad0741a2 --- /dev/null +++ b/Frontend/ui-library/.eslintignore @@ -0,0 +1,8 @@ +dist +node_modules +types +package-lock.json +package.json +tsconfig.json +webpack.config.js +.eslintrc.js \ No newline at end of file diff --git a/Frontend/ui-library/.eslintrc.js b/Frontend/ui-library/.eslintrc.js new file mode 100644 index 00000000..55691de9 --- /dev/null +++ b/Frontend/ui-library/.eslintrc.js @@ -0,0 +1,8 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'] +}; diff --git a/Frontend/ui-library/.prettierignore b/Frontend/ui-library/.prettierignore new file mode 100644 index 00000000..a9e12ff1 Binary files /dev/null and b/Frontend/ui-library/.prettierignore differ diff --git a/Frontend/ui-library/.prettierrc.json b/Frontend/ui-library/.prettierrc.json new file mode 100644 index 00000000..1661de8f --- /dev/null +++ b/Frontend/ui-library/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "none", + "tabWidth": 4, + "semi": true, + "singleQuote": true +} diff --git a/Frontend/ui-library/package-lock.json b/Frontend/ui-library/package-lock.json new file mode 100644 index 00000000..688f56de --- /dev/null +++ b/Frontend/ui-library/package-lock.json @@ -0,0 +1,6239 @@ +{ + "name": "@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.2", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.2", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@epicgames-ps/lib-pixelstreamingfrontend-ue5.2": "0.0.1", + "jss": "^10.9.2", + "jss-plugin-camel-case": "^10.9.2", + "jss-plugin-global": "^10.9.2" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.16.0", + "@typescript-eslint/parser": "^5.16.0", + "cspell": "^4.1.0", + "eslint": "^8.11.0", + "prettier": "2.8.3", + "ts-loader": "^9.4.2", + "typedoc": "^0.23.24", + "typescript": "^4.9.4", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1" + } + }, + "node_modules/@babel/runtime": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", + "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cspell/dict-aws": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-1.0.14.tgz", + "integrity": "sha512-K21CfB4ZpKYwwDQiPfic2zJA/uxkbsd4IQGejEvDAhE3z8wBs6g6BwwqdVO767M9NgZqc021yAVpr79N5pWe3w==", + "dev": true + }, + "node_modules/@cspell/dict-bash": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-1.0.18.tgz", + "integrity": "sha512-kJIqQ+FD2TCSgaaP5XLEDgy222+pVWTc+VhveNO++gnTWU3BCVjkD5LjfW7g/CmGONnz+nwXDueWspProaSdJw==", + "dev": true + }, + "node_modules/@cspell/dict-companies": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-1.0.40.tgz", + "integrity": "sha512-Aw07qiTroqSST2P5joSrC4uOA05zTXzI2wMb+me3q4Davv1D9sCkzXY0TGoC2vzhNv5ooemRi9KATGaBSdU1sw==", + "dev": true + }, + "node_modules/@cspell/dict-cpp": { + "version": "1.1.40", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-1.1.40.tgz", + "integrity": "sha512-sscfB3woNDNj60/yGXAdwNtIRWZ89y35xnIaJVDMk5TPMMpaDvuk0a34iOPIq0g4V+Y8e3RyAg71SH6ADwSjGw==", + "dev": true + }, + "node_modules/@cspell/dict-cryptocurrencies": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-1.0.10.tgz", + "integrity": "sha512-47ABvDJOkaST/rXipNMfNvneHUzASvmL6K/CbOFpYKfsd0x23Jc9k1yaOC7JAm82XSC/8a7+3Yu+Fk2jVJNnsA==", + "dev": true + }, + "node_modules/@cspell/dict-csharp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-1.0.11.tgz", + "integrity": "sha512-nub+ZCiTgmT87O+swI+FIAzNwaZPWUGckJU4GN402wBq420V+F4ZFqNV7dVALJrGaWH7LvADRtJxi6cZVHJKeA==", + "dev": true + }, + "node_modules/@cspell/dict-css": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-1.0.13.tgz", + "integrity": "sha512-HU8RbFRoGanFH85mT01Ot/Ay48ixr/gG25VPLtdq56QTrmPsw79gxYm/5Qay16eQbpoPIxaj5CAWNam+DX4GbA==", + "dev": true + }, + "node_modules/@cspell/dict-django": { + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-1.0.26.tgz", + "integrity": "sha512-mn9bd7Et1L2zuibc08GVHTiD2Go3/hdjyX5KLukXDklBkq06r+tb0OtKtf1zKodtFDTIaYekGADhNhA6AnKLkg==", + "dev": true + }, + "node_modules/@cspell/dict-dotnet": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-1.0.32.tgz", + "integrity": "sha512-9H9vXrgJB4KF8xsyTToXO53cXD33iyfrpT4mhCds+YLUw3P3x3E9myszgJzshnrxYBvQZ+QMII57Qr6SjZVk4Q==", + "dev": true + }, + "node_modules/@cspell/dict-elixir": { + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-1.0.26.tgz", + "integrity": "sha512-hz1yETUiRJM7yjN3mITSnxcmZaEyaBbyJhpZPpg+cKUil+xhHeZ2wwfbRc83QHGmlqEuDWbdCFqKSpCDJYpYhg==", + "dev": true + }, + "node_modules/@cspell/dict-en_us": { + "version": "1.2.45", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-1.2.45.tgz", + "integrity": "sha512-UPwR4rfiJCxnS+Py+EK9E4AUj3aPZE4p/yBRSHN+5aBQConlI0lLDtMceH5wlupA/sQTU1ERZGPJA9L96jVSyQ==", + "dev": true + }, + "node_modules/@cspell/dict-en-gb": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb/-/dict-en-gb-1.1.33.tgz", + "integrity": "sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==", + "dev": true + }, + "node_modules/@cspell/dict-filetypes": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-1.1.8.tgz", + "integrity": "sha512-EllahNkhzvLWo0ptwu0l3oEeAJOQSUpZnDfnKRIh6mJVehuSovNHwA9vrdZ8jBUjuqcfaN2e7c32zN0D/qvWJQ==", + "dev": true + }, + "node_modules/@cspell/dict-fonts": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-1.0.14.tgz", + "integrity": "sha512-VhIX+FVYAnqQrOuoFEtya6+H72J82cIicz9QddgknsTqZQ3dvgp6lmVnsQXPM3EnzA8n1peTGpLDwHzT7ociLA==", + "dev": true + }, + "node_modules/@cspell/dict-fullstack": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-1.0.39.tgz", + "integrity": "sha512-Mbi+zWdiP9yzL+X4YD9Tgcm5YQ95Ql+Y3vF2LRnOY6g2QWaijTRN1rgksVuxzpFqHi//+bx2uoUb0XEKBYDi8g==", + "dev": true + }, + "node_modules/@cspell/dict-golang": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-1.1.24.tgz", + "integrity": "sha512-qq3Cjnx2U1jpeWAGJL1GL0ylEhUMqyaR36Xij6Y6Aq4bViCRp+HRRqk0x5/IHHbOrti45h3yy7ii1itRFo+Xkg==", + "dev": true + }, + "node_modules/@cspell/dict-haskell": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-1.0.13.tgz", + "integrity": "sha512-kvl8T84cnYRPpND/P3D86P6WRSqebsbk0FnMfy27zo15L5MLAb3d3MOiT1kW3vEWfQgzUD7uddX/vUiuroQ8TA==", + "dev": true + }, + "node_modules/@cspell/dict-html": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-1.1.9.tgz", + "integrity": "sha512-vvnYia0tyIS5Fdoz+gEQm77MGZZE66kOJjuNpIYyRHCXFAhWdYz3SmkRm6YKJSWSvuO+WBJYTKDvkOxSh3Fx/w==", + "dev": true + }, + "node_modules/@cspell/dict-html-symbol-entities": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-1.0.23.tgz", + "integrity": "sha512-PV0UBgcBFbBLf/m1wfkVMM8w96kvfHoiCGLWO6BR3Q9v70IXoE4ae0+T+f0CkxcEkacMqEQk/I7vuE9MzrjaNw==", + "dev": true + }, + "node_modules/@cspell/dict-java": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-1.0.23.tgz", + "integrity": "sha512-LcOg9srYLDoNGd8n3kbfDBlZD+LOC9IVcnFCdua1b/luCHNVmlgBx7e677qPu7olpMYOD5TQIVW2OmM1+/6MFA==", + "dev": true + }, + "node_modules/@cspell/dict-latex": { + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-1.0.25.tgz", + "integrity": "sha512-cEgg91Migqcp1SdVV7dUeMxbPDhxdNo6Fgq2eygAXQjIOFK520FFvh/qxyBvW90qdZbIRoU2AJpchyHfGuwZFA==", + "dev": true + }, + "node_modules/@cspell/dict-lorem-ipsum": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-1.0.22.tgz", + "integrity": "sha512-yqzspR+2ADeAGUxLTfZ4pXvPl7FmkENMRcGDECmddkOiuEwBCWMZdMP5fng9B0Q6j91hQ8w9CLvJKBz10TqNYg==", + "dev": true + }, + "node_modules/@cspell/dict-lua": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-1.0.16.tgz", + "integrity": "sha512-YiHDt8kmHJ8nSBy0tHzaxiuitYp+oJ66ffCYuFWTNB3//Y0SI4OGHU3omLsQVeXIfCeVrO4DrVvRDoCls9B5zQ==", + "dev": true + }, + "node_modules/@cspell/dict-node": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-1.0.12.tgz", + "integrity": "sha512-RPNn/7CSkflAWk0sbSoOkg0ORrgBARUjOW3QjB11KwV1gSu8f5W/ij/S50uIXtlrfoBLqd4OyE04jyON+g/Xfg==", + "dev": true + }, + "node_modules/@cspell/dict-npm": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-1.0.16.tgz", + "integrity": "sha512-RwkuZGcYBxL3Yux3cSG/IOWGlQ1e9HLCpHeyMtTVGYKAIkFAVUnGrz20l16/Q7zUG7IEktBz5O42kAozrEnqMQ==", + "dev": true + }, + "node_modules/@cspell/dict-php": { + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-1.0.25.tgz", + "integrity": "sha512-RoBIP5MRdByyPaXcznZMfOY1JdCMYPPLua5E9gkq0TJO7bX5mC9hyAKfYBSWVQunZydd82HZixjb5MPkDFU1uw==", + "dev": true + }, + "node_modules/@cspell/dict-powershell": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-1.0.19.tgz", + "integrity": "sha512-zF/raM/lkhXeHf4I43OtK0gP9rBeEJFArscTVwLWOCIvNk21MJcNoTYoaGw+c056+Q+hJL0psGLO7QN+mxYH1A==", + "dev": true + }, + "node_modules/@cspell/dict-python": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-1.0.38.tgz", + "integrity": "sha512-KuyOQaby9NID/pn7EkXilpUxjVIvvyLzhr7BPsDS6FcvUE8Yhss6bJowEDHSv6pa+W2387phoqbDf2rTicquAA==", + "dev": true + }, + "node_modules/@cspell/dict-ruby": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-1.0.15.tgz", + "integrity": "sha512-I76hJA///lc1pgmDTGUFHN/O8KLIZIU/8TgIYIGI6Ix/YzSEvWNdQYbANn6JbCynS0X+7IbZ2Ft+QqvmGtIWuA==", + "dev": true + }, + "node_modules/@cspell/dict-rust": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-1.0.23.tgz", + "integrity": "sha512-lR4boDzs79YD6+30mmiSGAMMdwh7HTBAPUFSB0obR3Kidibfc3GZ+MHWZXay5dxZ4nBKM06vyjtanF9VJ8q1Iw==", + "dev": true + }, + "node_modules/@cspell/dict-scala": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-1.0.21.tgz", + "integrity": "sha512-5V/R7PRbbminTpPS3ywgdAalI9BHzcEjEj9ug4kWYvBIGwSnS7T6QCFCiu+e9LvEGUqQC+NHgLY4zs1NaBj2vA==", + "dev": true + }, + "node_modules/@cspell/dict-software-terms": { + "version": "1.0.48", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-1.0.48.tgz", + "integrity": "sha512-pfF3Ys2gRffu5ElqkH7FQMDMi/iZMyOzpGMb3FSH0PJ2AnRQ5rRNWght1h2L36YxvXl0mWVaFrrfwiOyRIc8ZQ==", + "dev": true + }, + "node_modules/@cspell/dict-typescript": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-1.0.20.tgz", + "integrity": "sha512-yIuGeeZtQA2gqpGefGjZqBl8iGJpIYWz0QzDqsscNi2qfSnLsbjM0RkRbTehM8y9gGGe7xfgUP5adxceJa5Krg==", + "dev": true + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@epicgames-ps/lib-pixelstreamingfrontend-ue5.2": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@epicgames-ps/lib-pixelstreamingfrontend-ue5.2/-/lib-pixelstreamingfrontend-dev-0.0.1.tgz", + "integrity": "sha512-dTx1ydL30hsIjHMfM9wG92vVnODpzsp+lZJi9zKkagqhGmKkvxkSBPnAIqbwJvqH0Yu4RozzQoZxRE7q6pBvWA==", + "dependencies": { + "jss": "^10.9.2", + "jss-plugin-camel-case": "^10.9.2", + "jss-plugin-global": "^10.9.2", + "sdp": "^3.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@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 + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@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, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@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, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/eslint": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.0.tgz", + "integrity": "sha512-35EhHNOXgxnUgh4XCJsGhE7zdlDhYDN/aMG6UbkByCFFNgQ7b3U+uVoqBpicFydR8JEfgdjCF7SJ7MiJfzuiTA==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "node_modules/@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==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", + "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/type-utils": "5.51.0", + "@typescript-eslint/utils": "5.51.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", + "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", + "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", + "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/utils": "5.51.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", + "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", + "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", + "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", + "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.51.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@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, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@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 + }, + "node_modules/@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 + }, + "node_modules/@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 + }, + "node_modules/@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, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@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 + }, + "node_modules/@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, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@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, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@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, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@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 + }, + "node_modules/@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, + "dependencies": { + "@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" + } + }, + "node_modules/@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, + "dependencies": { + "@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" + } + }, + "node_modules/@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, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@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, + "dependencies": { + "@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" + } + }, + "node_modules/@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, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@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 + }, + "node_modules/@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 + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/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, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/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, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/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 + }, + "node_modules/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/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 + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001451", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz", + "integrity": "sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/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, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/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, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "node_modules/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, + "engines": { + "node": ">= 10" + } + }, + "node_modules/comment-json": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz", + "integrity": "sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==", + "dev": true, + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cspell": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-4.2.8.tgz", + "integrity": "sha512-eqan8+lCU9bSp8Tl4+SR/ccBnuPyMmp7evck/RlMdFTjLh/s+3vQ5hQyBzbzK8w2MMqL84CymW7BwIOKjpylSg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "commander": "^7.0.0", + "comment-json": "^4.0.6", + "cspell-glob": "^0.1.25", + "cspell-lib": "^4.3.12", + "fs-extra": "^9.1.0", + "gensequence": "^3.1.1", + "get-stdin": "^8.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "cspell": "bin.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/cspell-glob": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-0.1.25.tgz", + "integrity": "sha512-/XaSHrGBpMJa+duFz3GKOWfrijrfdHT7a/XGgIcq3cymCSpOH+DPho42sl0jLI/hjM+8yv2m8aEoxRT8yVSnlg==", + "dev": true, + "dependencies": { + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/cspell-io": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-4.1.7.tgz", + "integrity": "sha512-V0/tUu9FnIS3v+vAvDT6NNa14Nc/zUNX8+YUUOfFAiDJJTdqefmvcWjOJBIMYBf3wIk9iWLmLbMM+bNHqr7DSQ==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.2", + "iterable-to-stream": "^1.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/cspell-lib": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-4.3.12.tgz", + "integrity": "sha512-yCCb6MoW1K8Tsr/WVEQoO4dfYhH9bCsjQayccb8MlyDaNNuWJHuX+gUGHsZSXSuChSh8PrTWKXJzs13/uM977g==", + "dev": true, + "dependencies": { + "@cspell/dict-aws": "^1.0.13", + "@cspell/dict-bash": "^1.0.11", + "@cspell/dict-companies": "^1.0.35", + "@cspell/dict-cpp": "^1.1.37", + "@cspell/dict-cryptocurrencies": "^1.0.10", + "@cspell/dict-csharp": "^1.0.10", + "@cspell/dict-css": "^1.0.10", + "@cspell/dict-django": "^1.0.25", + "@cspell/dict-dotnet": "^1.0.24", + "@cspell/dict-elixir": "^1.0.23", + "@cspell/dict-en_us": "^1.2.39", + "@cspell/dict-en-gb": "^1.1.27", + "@cspell/dict-filetypes": "^1.1.5", + "@cspell/dict-fonts": "^1.0.13", + "@cspell/dict-fullstack": "^1.0.36", + "@cspell/dict-golang": "^1.1.24", + "@cspell/dict-haskell": "^1.0.12", + "@cspell/dict-html": "^1.1.5", + "@cspell/dict-html-symbol-entities": "^1.0.23", + "@cspell/dict-java": "^1.0.22", + "@cspell/dict-latex": "^1.0.23", + "@cspell/dict-lorem-ipsum": "^1.0.22", + "@cspell/dict-lua": "^1.0.16", + "@cspell/dict-node": "^1.0.10", + "@cspell/dict-npm": "^1.0.10", + "@cspell/dict-php": "^1.0.23", + "@cspell/dict-powershell": "^1.0.14", + "@cspell/dict-python": "^1.0.32", + "@cspell/dict-ruby": "^1.0.12", + "@cspell/dict-rust": "^1.0.22", + "@cspell/dict-scala": "^1.0.21", + "@cspell/dict-software-terms": "^1.0.24", + "@cspell/dict-typescript": "^1.0.16", + "comment-json": "^4.1.0", + "configstore": "^5.0.1", + "cspell-io": "^4.1.7", + "cspell-trie-lib": "^4.2.8", + "cspell-util-bundle": "^4.1.11", + "fs-extra": "^9.1.0", + "gensequence": "^3.1.1", + "minimatch": "^3.0.4", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0", + "vscode-uri": "^3.0.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/cspell-trie-lib": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-4.2.8.tgz", + "integrity": "sha512-Nt3c0gxOYXIc3/yhALDukpje1BgR6guvlUKWQO2zb0r7qRWpwUw2j2YM4dWbHQeH/3Hx5ei4Braa6cMaiJ5YBw==", + "dev": true, + "dependencies": { + "gensequence": "^3.1.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/cspell-util-bundle": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/cspell-util-bundle/-/cspell-util-bundle-4.1.11.tgz", + "integrity": "sha512-or3OGKydZs1NwweMIgnA48k8H3F5zK4e5lonjUhpEzLYQZ2nB23decdoqZ8ogFC8pFTA40tZKDsMJ0b+65gX4Q==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/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 + }, + "node_modules/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, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.289", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.289.tgz", + "integrity": "sha512-relLdMfPBxqGCxy7Gyfm1HcbRPcFUJdlgnCPVgQ23sr1TvUrRJz0/QPoGP0+x41wOVSTN/Wi3w6YDgHiHJGOzg==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", + "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/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, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/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, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/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, + "engines": { + "node": ">=10" + } + }, + "node_modules/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, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/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, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/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, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/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==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@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" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/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==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/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, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/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, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensequence": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-3.1.1.tgz", + "integrity": "sha512-ys3h0hiteRwmY6BsvSttPmkhC0vEQHPJduANBRtH/dlDPZ0UBIb/dXy80IcckXyuQ6LKg+PloRqvGER9IS7F7g==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/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 + }, + "node_modules/global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", + "dev": true, + "dependencies": { + "ini": "^1.3.4" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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 + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/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, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" + }, + "node_modules/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, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/iterable-to-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterable-to-stream/-/iterable-to-stream-1.0.1.tgz", + "integrity": "sha512-O62gD5ADMUGtJoOoM9U6LQ7i4byPXUNoHJ6mqsmkQJcom331ZJGDApWgDESWyBMEHEJRjtHozgIiTzYo9RU4UA==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/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, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/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 + }, + "node_modules/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==", + "dev": true + }, + "node_modules/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": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jss": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.2.tgz", + "integrity": "sha512-b8G6rWpYLR4teTUbGd4I4EsnWjg7MN0Q5bSsjKhVkJVjhQDy2KzkbD2AW3TuT0RYZVmZZHKIrXDn6kjU14qkUg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "csstype": "^3.0.2", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/jss" + } + }, + "node_modules/jss-plugin-camel-case": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.2.tgz", + "integrity": "sha512-wgBPlL3WS0WDJ1lPJcgjux/SHnDuu7opmgQKSraKs4z8dCCyYMx9IDPFKBXQ8Q5dVYij1FFV0WdxyhuOOAXuTg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.9.2" + } + }, + "node_modules/jss-plugin-global": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.2.tgz", + "integrity": "sha512-GcX0aE8Ef6AtlasVrafg1DItlL/tWHoC4cGir4r3gegbWwF5ZOBYhx04gurPvWHC8F873aEGqge7C17xpwmp2g==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.2" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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 + }, + "node_modules/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, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/marked": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/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, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/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 + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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 + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/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, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/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, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-global": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", + "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", + "dev": true, + "dependencies": { + "global-dirs": "^0.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/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, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/sdp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.1.0.tgz", + "integrity": "sha512-YQ2+lulxfcPv5/3FsY9FR0F8r+qQngluz2L+YdR7S8mMiFFy3/6Cx9mzAL57Sbzgaj13Eaiq88rfQx7Wiik0qg==" + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/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, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.12.1.tgz", + "integrity": "sha512-aieaV1m349rZINEBkjxh2QbBvFFQOlgqYTNtCal82hHj4dDZ76oMlQIX+C7ryerBTDiga3e5NfH6smjdJ02BbQ==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, + "node_modules/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 + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/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, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.16.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.3.tgz", + "integrity": "sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/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 + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/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, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-loader": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/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, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typedoc": { + "version": "0.23.24", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.24.tgz", + "integrity": "sha512-bfmy8lNQh+WrPYcJbtjQ6JEEsVl/ce1ZIXyXhyW+a1vFrjO39t6J8sL/d6FfAGrJTc7McCXgk9AanYBSNvLdIA==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.2.5", + "minimatch": "^5.1.2", + "shiki": "^0.12.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 14.14" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, + "node_modules/vscode-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", + "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==", + "dev": true + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", + "colorette": "^2.0.14", + "commander": "^9.4.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "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, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@babel/runtime": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", + "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@cspell/dict-aws": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-1.0.14.tgz", + "integrity": "sha512-K21CfB4ZpKYwwDQiPfic2zJA/uxkbsd4IQGejEvDAhE3z8wBs6g6BwwqdVO767M9NgZqc021yAVpr79N5pWe3w==", + "dev": true + }, + "@cspell/dict-bash": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-1.0.18.tgz", + "integrity": "sha512-kJIqQ+FD2TCSgaaP5XLEDgy222+pVWTc+VhveNO++gnTWU3BCVjkD5LjfW7g/CmGONnz+nwXDueWspProaSdJw==", + "dev": true + }, + "@cspell/dict-companies": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-1.0.40.tgz", + "integrity": "sha512-Aw07qiTroqSST2P5joSrC4uOA05zTXzI2wMb+me3q4Davv1D9sCkzXY0TGoC2vzhNv5ooemRi9KATGaBSdU1sw==", + "dev": true + }, + "@cspell/dict-cpp": { + "version": "1.1.40", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-1.1.40.tgz", + "integrity": "sha512-sscfB3woNDNj60/yGXAdwNtIRWZ89y35xnIaJVDMk5TPMMpaDvuk0a34iOPIq0g4V+Y8e3RyAg71SH6ADwSjGw==", + "dev": true + }, + "@cspell/dict-cryptocurrencies": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-1.0.10.tgz", + "integrity": "sha512-47ABvDJOkaST/rXipNMfNvneHUzASvmL6K/CbOFpYKfsd0x23Jc9k1yaOC7JAm82XSC/8a7+3Yu+Fk2jVJNnsA==", + "dev": true + }, + "@cspell/dict-csharp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-1.0.11.tgz", + "integrity": "sha512-nub+ZCiTgmT87O+swI+FIAzNwaZPWUGckJU4GN402wBq420V+F4ZFqNV7dVALJrGaWH7LvADRtJxi6cZVHJKeA==", + "dev": true + }, + "@cspell/dict-css": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-1.0.13.tgz", + "integrity": "sha512-HU8RbFRoGanFH85mT01Ot/Ay48ixr/gG25VPLtdq56QTrmPsw79gxYm/5Qay16eQbpoPIxaj5CAWNam+DX4GbA==", + "dev": true + }, + "@cspell/dict-django": { + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-1.0.26.tgz", + "integrity": "sha512-mn9bd7Et1L2zuibc08GVHTiD2Go3/hdjyX5KLukXDklBkq06r+tb0OtKtf1zKodtFDTIaYekGADhNhA6AnKLkg==", + "dev": true + }, + "@cspell/dict-dotnet": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-1.0.32.tgz", + "integrity": "sha512-9H9vXrgJB4KF8xsyTToXO53cXD33iyfrpT4mhCds+YLUw3P3x3E9myszgJzshnrxYBvQZ+QMII57Qr6SjZVk4Q==", + "dev": true + }, + "@cspell/dict-elixir": { + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-1.0.26.tgz", + "integrity": "sha512-hz1yETUiRJM7yjN3mITSnxcmZaEyaBbyJhpZPpg+cKUil+xhHeZ2wwfbRc83QHGmlqEuDWbdCFqKSpCDJYpYhg==", + "dev": true + }, + "@cspell/dict-en_us": { + "version": "1.2.45", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-1.2.45.tgz", + "integrity": "sha512-UPwR4rfiJCxnS+Py+EK9E4AUj3aPZE4p/yBRSHN+5aBQConlI0lLDtMceH5wlupA/sQTU1ERZGPJA9L96jVSyQ==", + "dev": true + }, + "@cspell/dict-en-gb": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb/-/dict-en-gb-1.1.33.tgz", + "integrity": "sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==", + "dev": true + }, + "@cspell/dict-filetypes": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-1.1.8.tgz", + "integrity": "sha512-EllahNkhzvLWo0ptwu0l3oEeAJOQSUpZnDfnKRIh6mJVehuSovNHwA9vrdZ8jBUjuqcfaN2e7c32zN0D/qvWJQ==", + "dev": true + }, + "@cspell/dict-fonts": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-1.0.14.tgz", + "integrity": "sha512-VhIX+FVYAnqQrOuoFEtya6+H72J82cIicz9QddgknsTqZQ3dvgp6lmVnsQXPM3EnzA8n1peTGpLDwHzT7ociLA==", + "dev": true + }, + "@cspell/dict-fullstack": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-1.0.39.tgz", + "integrity": "sha512-Mbi+zWdiP9yzL+X4YD9Tgcm5YQ95Ql+Y3vF2LRnOY6g2QWaijTRN1rgksVuxzpFqHi//+bx2uoUb0XEKBYDi8g==", + "dev": true + }, + "@cspell/dict-golang": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-1.1.24.tgz", + "integrity": "sha512-qq3Cjnx2U1jpeWAGJL1GL0ylEhUMqyaR36Xij6Y6Aq4bViCRp+HRRqk0x5/IHHbOrti45h3yy7ii1itRFo+Xkg==", + "dev": true + }, + "@cspell/dict-haskell": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-1.0.13.tgz", + "integrity": "sha512-kvl8T84cnYRPpND/P3D86P6WRSqebsbk0FnMfy27zo15L5MLAb3d3MOiT1kW3vEWfQgzUD7uddX/vUiuroQ8TA==", + "dev": true + }, + "@cspell/dict-html": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-1.1.9.tgz", + "integrity": "sha512-vvnYia0tyIS5Fdoz+gEQm77MGZZE66kOJjuNpIYyRHCXFAhWdYz3SmkRm6YKJSWSvuO+WBJYTKDvkOxSh3Fx/w==", + "dev": true + }, + "@cspell/dict-html-symbol-entities": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-1.0.23.tgz", + "integrity": "sha512-PV0UBgcBFbBLf/m1wfkVMM8w96kvfHoiCGLWO6BR3Q9v70IXoE4ae0+T+f0CkxcEkacMqEQk/I7vuE9MzrjaNw==", + "dev": true + }, + "@cspell/dict-java": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-1.0.23.tgz", + "integrity": "sha512-LcOg9srYLDoNGd8n3kbfDBlZD+LOC9IVcnFCdua1b/luCHNVmlgBx7e677qPu7olpMYOD5TQIVW2OmM1+/6MFA==", + "dev": true + }, + "@cspell/dict-latex": { + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-1.0.25.tgz", + "integrity": "sha512-cEgg91Migqcp1SdVV7dUeMxbPDhxdNo6Fgq2eygAXQjIOFK520FFvh/qxyBvW90qdZbIRoU2AJpchyHfGuwZFA==", + "dev": true + }, + "@cspell/dict-lorem-ipsum": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-1.0.22.tgz", + "integrity": "sha512-yqzspR+2ADeAGUxLTfZ4pXvPl7FmkENMRcGDECmddkOiuEwBCWMZdMP5fng9B0Q6j91hQ8w9CLvJKBz10TqNYg==", + "dev": true + }, + "@cspell/dict-lua": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-1.0.16.tgz", + "integrity": "sha512-YiHDt8kmHJ8nSBy0tHzaxiuitYp+oJ66ffCYuFWTNB3//Y0SI4OGHU3omLsQVeXIfCeVrO4DrVvRDoCls9B5zQ==", + "dev": true + }, + "@cspell/dict-node": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-1.0.12.tgz", + "integrity": "sha512-RPNn/7CSkflAWk0sbSoOkg0ORrgBARUjOW3QjB11KwV1gSu8f5W/ij/S50uIXtlrfoBLqd4OyE04jyON+g/Xfg==", + "dev": true + }, + "@cspell/dict-npm": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-1.0.16.tgz", + "integrity": "sha512-RwkuZGcYBxL3Yux3cSG/IOWGlQ1e9HLCpHeyMtTVGYKAIkFAVUnGrz20l16/Q7zUG7IEktBz5O42kAozrEnqMQ==", + "dev": true + }, + "@cspell/dict-php": { + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-1.0.25.tgz", + "integrity": "sha512-RoBIP5MRdByyPaXcznZMfOY1JdCMYPPLua5E9gkq0TJO7bX5mC9hyAKfYBSWVQunZydd82HZixjb5MPkDFU1uw==", + "dev": true + }, + "@cspell/dict-powershell": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-1.0.19.tgz", + "integrity": "sha512-zF/raM/lkhXeHf4I43OtK0gP9rBeEJFArscTVwLWOCIvNk21MJcNoTYoaGw+c056+Q+hJL0psGLO7QN+mxYH1A==", + "dev": true + }, + "@cspell/dict-python": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-1.0.38.tgz", + "integrity": "sha512-KuyOQaby9NID/pn7EkXilpUxjVIvvyLzhr7BPsDS6FcvUE8Yhss6bJowEDHSv6pa+W2387phoqbDf2rTicquAA==", + "dev": true + }, + "@cspell/dict-ruby": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-1.0.15.tgz", + "integrity": "sha512-I76hJA///lc1pgmDTGUFHN/O8KLIZIU/8TgIYIGI6Ix/YzSEvWNdQYbANn6JbCynS0X+7IbZ2Ft+QqvmGtIWuA==", + "dev": true + }, + "@cspell/dict-rust": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-1.0.23.tgz", + "integrity": "sha512-lR4boDzs79YD6+30mmiSGAMMdwh7HTBAPUFSB0obR3Kidibfc3GZ+MHWZXay5dxZ4nBKM06vyjtanF9VJ8q1Iw==", + "dev": true + }, + "@cspell/dict-scala": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-1.0.21.tgz", + "integrity": "sha512-5V/R7PRbbminTpPS3ywgdAalI9BHzcEjEj9ug4kWYvBIGwSnS7T6QCFCiu+e9LvEGUqQC+NHgLY4zs1NaBj2vA==", + "dev": true + }, + "@cspell/dict-software-terms": { + "version": "1.0.48", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-1.0.48.tgz", + "integrity": "sha512-pfF3Ys2gRffu5ElqkH7FQMDMi/iZMyOzpGMb3FSH0PJ2AnRQ5rRNWght1h2L36YxvXl0mWVaFrrfwiOyRIc8ZQ==", + "dev": true + }, + "@cspell/dict-typescript": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-1.0.20.tgz", + "integrity": "sha512-yIuGeeZtQA2gqpGefGjZqBl8iGJpIYWz0QzDqsscNi2qfSnLsbjM0RkRbTehM8y9gGGe7xfgUP5adxceJa5Krg==", + "dev": true + }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, + "@epicgames-ps/lib-pixelstreamingfrontend-ue5.2": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@epicgames-ps/lib-pixelstreamingfrontend-ue5.2/-/lib-pixelstreamingfrontend-dev-0.0.1.tgz", + "integrity": "sha512-dTx1ydL30hsIjHMfM9wG92vVnODpzsp+lZJi9zKkagqhGmKkvxkSBPnAIqbwJvqH0Yu4RozzQoZxRE7q6pBvWA==", + "requires": { + "jss": "^10.9.2", + "jss-plugin-camel-case": "^10.9.2", + "jss-plugin-global": "^10.9.2", + "sdp": "^3.1.0" + } + }, + "@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@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 + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@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": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@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 + }, + "@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": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@types/eslint": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.0.tgz", + "integrity": "sha512-35EhHNOXgxnUgh4XCJsGhE7zdlDhYDN/aMG6UbkByCFFNgQ7b3U+uVoqBpicFydR8JEfgdjCF7SJ7MiJfzuiTA==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "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==", + "dev": true + }, + "@types/node": { + "version": "18.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", + "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==", + "dev": true + }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.51.0.tgz", + "integrity": "sha512-wcAwhEWm1RgNd7dxD/o+nnLW8oH+6RK1OGnmbmkj/GGoDPV1WWMVP0FXYQBivKHdwM1pwii3bt//RC62EriIUQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/type-utils": "5.51.0", + "@typescript-eslint/utils": "5.51.0", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.51.0.tgz", + "integrity": "sha512-fEV0R9gGmfpDeRzJXn+fGQKcl0inIeYobmmUWijZh9zA7bxJ8clPhV9up2ZQzATxAiFAECqPQyMDB4o4B81AaA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.51.0.tgz", + "integrity": "sha512-gNpxRdlx5qw3yaHA0SFuTjW4rxeYhpHxt491PEcKF8Z6zpq0kMhe0Tolxt0qjlojS+/wArSDlj/LtE69xUJphQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.51.0.tgz", + "integrity": "sha512-QHC5KKyfV8sNSyHqfNa0UbTbJ6caB8uhcx2hYcWVvJAZYJRBo5HyyZfzMdRx8nvS+GyMg56fugMzzWnojREuQQ==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/utils": "5.51.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.51.0.tgz", + "integrity": "sha512-SqOn0ANn/v6hFn0kjvLwiDi4AzR++CBZz0NV5AnusT2/3y32jdc0G4woXPWHCumWtUXZKPAS27/9vziSsC9jnw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.51.0.tgz", + "integrity": "sha512-TSkNupHvNRkoH9FMA3w7TazVFcBPveAAmb7Sz+kArY6sLT86PA5Vx80cKlYmd8m3Ha2SwofM1KwraF24lM9FvA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.51.0.tgz", + "integrity": "sha512-76qs+5KWcaatmwtwsDJvBk4H76RJQBFe+Gext0EfJdC3Vd2kpY2Pf//OHHzHp84Ciw0/rYoGTDnIAr3uWhhJYw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.51.0.tgz", + "integrity": "sha512-Oh2+eTdjHjOFjKA27sxESlA87YPSOJafGCR0md5oeMdh1ZcCfAGCIOL216uTBAkAIptvLIfKQhl7lHxMJet4GQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.51.0", + "eslint-visitor-keys": "^3.3.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-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-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" + } + }, + "@webpack-cli/configtest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "dev": true, + "requires": {} + }, + "@webpack-cli/serve": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", + "dev": true, + "requires": {} + }, + "@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 + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "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, + "requires": {} + }, + "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, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "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" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "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 + }, + "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 + }, + "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 + }, + "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" + } + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "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 + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001451", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz", + "integrity": "sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w==", + "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" + } + }, + "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 + }, + "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" + } + }, + "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 + }, + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "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 + }, + "comment-json": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz", + "integrity": "sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==", + "dev": true, + "requires": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "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==", + "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" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, + "cspell": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-4.2.8.tgz", + "integrity": "sha512-eqan8+lCU9bSp8Tl4+SR/ccBnuPyMmp7evck/RlMdFTjLh/s+3vQ5hQyBzbzK8w2MMqL84CymW7BwIOKjpylSg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "commander": "^7.0.0", + "comment-json": "^4.0.6", + "cspell-glob": "^0.1.25", + "cspell-lib": "^4.3.12", + "fs-extra": "^9.1.0", + "gensequence": "^3.1.1", + "get-stdin": "^8.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + } + }, + "cspell-glob": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-0.1.25.tgz", + "integrity": "sha512-/XaSHrGBpMJa+duFz3GKOWfrijrfdHT7a/XGgIcq3cymCSpOH+DPho42sl0jLI/hjM+8yv2m8aEoxRT8yVSnlg==", + "dev": true, + "requires": { + "micromatch": "^4.0.2" + } + }, + "cspell-io": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-4.1.7.tgz", + "integrity": "sha512-V0/tUu9FnIS3v+vAvDT6NNa14Nc/zUNX8+YUUOfFAiDJJTdqefmvcWjOJBIMYBf3wIk9iWLmLbMM+bNHqr7DSQ==", + "dev": true, + "requires": { + "iconv-lite": "^0.6.2", + "iterable-to-stream": "^1.0.1" + } + }, + "cspell-lib": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-4.3.12.tgz", + "integrity": "sha512-yCCb6MoW1K8Tsr/WVEQoO4dfYhH9bCsjQayccb8MlyDaNNuWJHuX+gUGHsZSXSuChSh8PrTWKXJzs13/uM977g==", + "dev": true, + "requires": { + "@cspell/dict-aws": "^1.0.13", + "@cspell/dict-bash": "^1.0.11", + "@cspell/dict-companies": "^1.0.35", + "@cspell/dict-cpp": "^1.1.37", + "@cspell/dict-cryptocurrencies": "^1.0.10", + "@cspell/dict-csharp": "^1.0.10", + "@cspell/dict-css": "^1.0.10", + "@cspell/dict-django": "^1.0.25", + "@cspell/dict-dotnet": "^1.0.24", + "@cspell/dict-elixir": "^1.0.23", + "@cspell/dict-en_us": "^1.2.39", + "@cspell/dict-en-gb": "^1.1.27", + "@cspell/dict-filetypes": "^1.1.5", + "@cspell/dict-fonts": "^1.0.13", + "@cspell/dict-fullstack": "^1.0.36", + "@cspell/dict-golang": "^1.1.24", + "@cspell/dict-haskell": "^1.0.12", + "@cspell/dict-html": "^1.1.5", + "@cspell/dict-html-symbol-entities": "^1.0.23", + "@cspell/dict-java": "^1.0.22", + "@cspell/dict-latex": "^1.0.23", + "@cspell/dict-lorem-ipsum": "^1.0.22", + "@cspell/dict-lua": "^1.0.16", + "@cspell/dict-node": "^1.0.10", + "@cspell/dict-npm": "^1.0.10", + "@cspell/dict-php": "^1.0.23", + "@cspell/dict-powershell": "^1.0.14", + "@cspell/dict-python": "^1.0.32", + "@cspell/dict-ruby": "^1.0.12", + "@cspell/dict-rust": "^1.0.22", + "@cspell/dict-scala": "^1.0.21", + "@cspell/dict-software-terms": "^1.0.24", + "@cspell/dict-typescript": "^1.0.16", + "comment-json": "^4.1.0", + "configstore": "^5.0.1", + "cspell-io": "^4.1.7", + "cspell-trie-lib": "^4.2.8", + "cspell-util-bundle": "^4.1.11", + "fs-extra": "^9.1.0", + "gensequence": "^3.1.1", + "minimatch": "^3.0.4", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0", + "vscode-uri": "^3.0.2" + } + }, + "cspell-trie-lib": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-4.2.8.tgz", + "integrity": "sha512-Nt3c0gxOYXIc3/yhALDukpje1BgR6guvlUKWQO2zb0r7qRWpwUw2j2YM4dWbHQeH/3Hx5ei4Braa6cMaiJ5YBw==", + "dev": true, + "requires": { + "gensequence": "^3.1.1" + } + }, + "cspell-util-bundle": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/cspell-util-bundle/-/cspell-util-bundle-4.1.11.tgz", + "integrity": "sha512-or3OGKydZs1NwweMIgnA48k8H3F5zK4e5lonjUhpEzLYQZ2nB23decdoqZ8ogFC8pFTA40tZKDsMJ0b+65gX4Q==", + "dev": true + }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + }, + "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" + } + }, + "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 + }, + "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" + } + }, + "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" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "electron-to-chromium": { + "version": "1.4.289", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.289.tgz", + "integrity": "sha512-relLdMfPBxqGCxy7Gyfm1HcbRPcFUJdlgnCPVgQ23sr1TvUrRJz0/QPoGP0+x41wOVSTN/Wi3w6YDgHiHJGOzg==", + "dev": true + }, + "enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true + }, + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "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": { + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz", + "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "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==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + } + } + }, + "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" + } + }, + "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": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "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 + } + } + }, + "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 + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.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==", + "dev": true, + "requires": { + "estraverse": "^5.1.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 + } + } + }, + "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": { + "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 + } + } + }, + "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 + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "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==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "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" + } + }, + "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==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "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" + } + }, + "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": { + "to-regex-range": "^5.0.1" + } + }, + "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" + } + }, + "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.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "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" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": 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 + }, + "gensequence": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-3.1.1.tgz", + "integrity": "sha512-ys3h0hiteRwmY6BsvSttPmkhC0vEQHPJduANBRtH/dlDPZ0UBIb/dXy80IcckXyuQ6LKg+PloRqvGER9IS7F7g==", + "dev": true + }, + "get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "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" + } + }, + "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" + } + }, + "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 + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "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" + } + }, + "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 + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "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" + } + }, + "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 + }, + "has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true + }, + "hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, + "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": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "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": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "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 + } + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "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-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" + }, + "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-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "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": { + "isobject": "^3.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "iterable-to-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterable-to-stream/-/iterable-to-stream-1.0.1.tgz", + "integrity": "sha512-O62gD5ADMUGtJoOoM9U6LQ7i4byPXUNoHJ6mqsmkQJcom331ZJGDApWgDESWyBMEHEJRjtHozgIiTzYo9RU4UA==", + "dev": true + }, + "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": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "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-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "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 + }, + "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==", + "dev": true + }, + "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": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jss": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.2.tgz", + "integrity": "sha512-b8G6rWpYLR4teTUbGd4I4EsnWjg7MN0Q5bSsjKhVkJVjhQDy2KzkbD2AW3TuT0RYZVmZZHKIrXDn6kjU14qkUg==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^3.0.2", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-camel-case": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.2.tgz", + "integrity": "sha512-wgBPlL3WS0WDJ1lPJcgjux/SHnDuu7opmgQKSraKs4z8dCCyYMx9IDPFKBXQ8Q5dVYij1FFV0WdxyhuOOAXuTg==", + "requires": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.9.2" + } + }, + "jss-plugin-global": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.2.tgz", + "integrity": "sha512-GcX0aE8Ef6AtlasVrafg1DItlL/tWHoC4cGir4r3gegbWwF5ZOBYhx04gurPvWHC8F873aEGqge7C17xpwmp2g==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.2" + } + }, + "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 + }, + "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" + } + }, + "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 + }, + "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" + } + }, + "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 + }, + "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": { + "yallist": "^4.0.0" + } + }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "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": { + "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 + } + } + }, + "marked": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==", + "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 + }, + "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": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "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": { + "mime-db": "1.52.0" + } + }, + "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" + } + }, + "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 + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "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 + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "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" + } + }, + "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" + } + }, + "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 + }, + "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, + "requires": { + "callsites": "^3.0.0" + } + }, + "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==", + "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": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "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 + }, + "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-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 + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "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==", + "dev": true, + "requires": { + "find-up": "^4.0.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" + } + }, + "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" + } + }, + "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": "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": { + "p-limit": "^2.2.0" + } + } + } + }, + "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 + }, + "prettier": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", + "dev": true + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "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==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "requires": { + "resolve": "^1.20.0" + } + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^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 + }, + "resolve-global": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", + "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", + "dev": true, + "requires": { + "global-dirs": "^0.1.1" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "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": { + "glob": "^7.1.3" + } + }, + "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": { + "queue-microtask": "^1.2.2" + } + }, + "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 + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "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==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "sdp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.1.0.tgz", + "integrity": "sha512-YQ2+lulxfcPv5/3FsY9FR0F8r+qQngluz2L+YdR7S8mMiFFy3/6Cx9mzAL57Sbzgaj13Eaiq88rfQx7Wiik0qg==" + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "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": { + "kind-of": "^6.0.2" + } + }, + "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 + }, + "shiki": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.12.1.tgz", + "integrity": "sha512-aieaV1m349rZINEBkjxh2QbBvFFQOlgqYTNtCal82hHj4dDZ76oMlQIX+C7ryerBTDiga3e5NfH6smjdJ02BbQ==", + "dev": true, + "requires": { + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, + "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 + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "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" + } + }, + "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-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 + }, + "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" + } + }, + "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 + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "terser": { + "version": "5.16.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.3.tgz", + "integrity": "sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.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 + } + } + }, + "terser-webpack-plugin": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "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": { + "is-number": "^7.0.0" + } + }, + "ts-loader": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "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 + }, + "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": { + "is-typedarray": "^1.0.0" + } + }, + "typedoc": { + "version": "0.23.24", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.24.tgz", + "integrity": "sha512-bfmy8lNQh+WrPYcJbtjQ6JEEsVl/ce1ZIXyXhyW+a1vFrjO39t6J8sL/d6FfAGrJTc7McCXgk9AanYBSNvLdIA==", + "dev": true, + "requires": { + "lunr": "^2.3.9", + "marked": "^4.2.5", + "minimatch": "^5.1.2", + "shiki": "^0.12.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, + "vscode-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", + "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==", + "dev": true + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "webpack": { + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + } + }, + "webpack-cli": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", + "colorette": "^2.0.14", + "commander": "^9.4.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true + } + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "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 + }, + "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" + } + }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "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 + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "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" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/Frontend/ui-library/package.json b/Frontend/ui-library/package.json new file mode 100644 index 00000000..332106b0 --- /dev/null +++ b/Frontend/ui-library/package.json @@ -0,0 +1,43 @@ +{ + "name": "@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.2", + "version": "0.0.1", + "description": "Frontend library default UI for Pixel Streaming", + "main": "dist/lib-pixelstreamingfrontend-ui.js", + "types": "types/pixelstreamingfrontend-ui.d.ts", + "scripts": { + "compile": "tsc --build --clean && tsc", + "build": "npx webpack --config webpack.prod.js", + "build-dev": "npx webpack --config webpack.dev.js", + "build-all": "cd ../library && npm run build && cd ../ui-library && npm link ../library && npx webpack --config webpack.prod.js", + "build-dev-all": "cd ../library && npm run build-dev && cd ../ui-library && npm link ../library & npx webpack --config webpack.dev.js", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "spellcheck": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.16.0", + "@typescript-eslint/parser": "^5.16.0", + "cspell": "^4.1.0", + "eslint": "^8.11.0", + "prettier": "2.8.3", + "ts-loader": "^9.4.2", + "typedoc": "^0.23.24", + "typescript": "^4.9.4", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1" + }, + "dependencies": { + "@epicgames-ps/lib-pixelstreamingfrontend-ue5.2": "0.0.1", + "jss": "^10.9.2", + "jss-plugin-camel-case": "^10.9.2", + "jss-plugin-global": "^10.9.2" + }, + "repository": { + "type": "git", + "url": "https://github.com/EpicGames/PixelStreamingInfrastructure.git" + }, + "author": "Epic Games", + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/Frontend/ui-library/src/Application/Application.ts b/Frontend/ui-library/src/Application/Application.ts new file mode 100644 index 00000000..84d81da9 --- /dev/null +++ b/Frontend/ui-library/src/Application/Application.ts @@ -0,0 +1,617 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import { + PixelStreaming, + Flags, + Logger, + AggregatedStats, + LatencyTestResults, + InitialSettings, + MessageStreamerList +} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { OverlayBase } from '../Overlay/BaseOverlay'; +import { ActionOverlay } from '../Overlay/ActionOverlay'; +import { TextOverlay } from '../Overlay/TextOverlay'; +import { ConnectOverlay } from '../Overlay/ConnectOverlay'; +import { DisconnectOverlay } from '../Overlay/DisconnectOverlay'; +import { PlayOverlay } from '../Overlay/PlayOverlay'; +import { InfoOverlay } from '../Overlay/InfoOverlay'; +import { ErrorOverlay } from '../Overlay/ErrorOverlay'; +import { AFKOverlay } from '../Overlay/AFKOverlay'; +import { Controls } from '../UI/Controls'; +import { LabelledButton } from '../UI/LabelledButton'; +import { SettingsPanel } from '../UI/SettingsPanel'; +import { StatsPanel } from '../UI/StatsPanel'; +import { VideoQpIndicator } from '../UI/VideoQpIndicator'; +import { ConfigUI, LightMode } from '../Config/ConfigUI'; + +export interface UIOptions { + pixelStreaming: PixelStreaming; +} + +/** + * Provides common base functionality for applications that extend this application + */ +export class Application { + pixelStreaming: PixelStreaming; + + _rootElement: HTMLElement; + _uiFeatureElement: HTMLElement; + + // set the overlay placeholders + currentOverlay: OverlayBase | null; + disconnectOverlay: ActionOverlay; + connectOverlay: ActionOverlay; + playOverlay: ActionOverlay; + infoOverlay: TextOverlay; + errorOverlay: TextOverlay; + afkOverlay: AFKOverlay; + + controls: Controls; + + settingsPanel: SettingsPanel; + statsPanel: StatsPanel; + videoQpIndicator: VideoQpIndicator; + + configUI: ConfigUI; + + /** + * @param options - Initialization options + */ + constructor(options: UIOptions) { + this.pixelStreaming = options.pixelStreaming; + this.configUI = new ConfigUI(this.pixelStreaming.config); + + this.createOverlays(); + + // Add stats panel + this.statsPanel = new StatsPanel(); + this.uiFeaturesElement.appendChild(this.statsPanel.rootElement); + + // Add settings panel + this.settingsPanel = new SettingsPanel(); + this.uiFeaturesElement.appendChild(this.settingsPanel.rootElement); + + // Add the video stream QP indicator + this.videoQpIndicator = new VideoQpIndicator(); + this.uiFeaturesElement.appendChild(this.videoQpIndicator.rootElement); + + this.configureSettings(); + + this.createButtons(); + + this.registerCallbacks(); + + this.showConnectOrAutoConnectOverlays(); + + this.updateColors(this.configUI.isCustomFlagEnabled(LightMode)); + } + + public createOverlays(): void { + // build all of the overlays + this.disconnectOverlay = new DisconnectOverlay( + this.pixelStreaming.videoElementParent + ); + this.connectOverlay = new ConnectOverlay( + this.pixelStreaming.videoElementParent + ); + this.playOverlay = new PlayOverlay( + this.pixelStreaming.videoElementParent + ); + this.infoOverlay = new InfoOverlay( + this.pixelStreaming.videoElementParent + ); + this.errorOverlay = new ErrorOverlay( + this.pixelStreaming.videoElementParent + ); + this.afkOverlay = new AFKOverlay( + this.pixelStreaming.videoElementParent + ); + + this.disconnectOverlay.onAction(() => this.pixelStreaming.reconnect()); + + // Build the webRtc connect overlay Event Listener and show the connect overlay + this.connectOverlay.onAction(() => this.pixelStreaming.connect()); + + // set up the play overlays action + this.playOverlay.onAction(() => this.pixelStreaming.play()); + } + + /** + * Set up button click functions and button functionality + */ + public createButtons() { + // Setup controls + const controls = new Controls(); + + // When we fullscreen we want this element to be the root + controls.fullscreenIcon.fullscreenElement = this.rootElement; + this.uiFeaturesElement.appendChild(controls.rootElement); + + // Add settings button to controls + controls.settingsIcon.rootElement.onclick = () => + this.settingsClicked(); + this.settingsPanel.settingsCloseButton.onclick = () => + this.settingsClicked(); + + // Add WebXR button to controls + controls.xrIcon.rootElement.onclick = () => + this.pixelStreaming.toggleXR(); + + // setup the stats/info button + controls.statsIcon.rootElement.onclick = () => this.statsClicked(); + + this.statsPanel.statsCloseButton.onclick = () => this.statsClicked(); + + // Add button for toggle fps + const showFPSButton = new LabelledButton('Show FPS', 'Toggle'); + showFPSButton.addOnClickListener(() => { + this.pixelStreaming.requestShowFps(); + }); + + // Add button for restart stream + const restartStreamButton = new LabelledButton( + 'Restart Stream', + 'Restart' + ); + restartStreamButton.addOnClickListener(() => { + this.pixelStreaming.reconnect(); + }); + + // Add button for request keyframe + const requestKeyframeButton = new LabelledButton( + 'Request keyframe', + 'Request' + ); + requestKeyframeButton.addOnClickListener(() => { + this.pixelStreaming.requestIframe(); + }); + + const commandsSectionElem = this.configUI.buildSectionWithHeading( + this.settingsPanel.settingsContentElement, + 'Commands' + ); + commandsSectionElem.appendChild(showFPSButton.rootElement); + commandsSectionElem.appendChild(requestKeyframeButton.rootElement); + commandsSectionElem.appendChild(restartStreamButton.rootElement); + } + + /** + * Configure the settings with on change listeners and any additional per experience settings. + */ + configureSettings(): void { + // This builds all the settings sections and flags under this `settingsContent` element. + this.configUI.populateSettingsElement( + this.settingsPanel.settingsContentElement + ); + + this.configUI.addCustomFlagOnSettingChangedListener( + LightMode, + (isLightMode: boolean) => { + this.configUI.setCustomFlagLabel( + LightMode, + `Color Scheme: ${isLightMode ? 'Light' : 'Dark'} Mode` + ); + this.updateColors(isLightMode); + } + ); + } + + registerCallbacks() { + this.pixelStreaming.addEventListener( + 'afkWarningActivate', + ({ data: { countDown, dismissAfk } }) => + this.showAfkOverlay(countDown, dismissAfk) + ); + this.pixelStreaming.addEventListener( + 'afkWarningUpdate', + ({ data: { countDown } }) => + this.afkOverlay.updateCountdown(countDown) + ); + this.pixelStreaming.addEventListener( + 'afkWarningDeactivate', + () => this.afkOverlay.hide() + ); + this.pixelStreaming.addEventListener('afkTimedOut', () => + this.afkOverlay.hide() + ); + this.pixelStreaming.addEventListener( + 'videoEncoderAvgQP', + ({ data: { avgQP } }) => this.onVideoEncoderAvgQP(avgQP) + ); + this.pixelStreaming.addEventListener('webRtcSdp', () => + this.onWebRtcSdp() + ); + this.pixelStreaming.addEventListener('webRtcAutoConnect', () => + this.onWebRtcAutoConnect() + ); + this.pixelStreaming.addEventListener('webRtcConnecting', () => + this.onWebRtcConnecting() + ); + this.pixelStreaming.addEventListener('webRtcConnected', () => + this.onWebRtcConnected() + ); + this.pixelStreaming.addEventListener('webRtcFailed', () => + this.onWebRtcFailed() + ); + this.pixelStreaming.addEventListener( + 'webRtcDisconnected', + ({ data: { eventString, showActionOrErrorOnDisconnect } }) => + this.onDisconnect(eventString, showActionOrErrorOnDisconnect) + ); + this.pixelStreaming.addEventListener('videoInitialized', () => + this.onVideoInitialized() + ); + this.pixelStreaming.addEventListener('streamLoading', () => + this.onStreamLoading() + ); + this.pixelStreaming.addEventListener( + 'playStreamError', + ({ data: { message } }) => this.onPlayStreamError(message) + ); + this.pixelStreaming.addEventListener('playStream', () => + this.onPlayStream() + ); + this.pixelStreaming.addEventListener( + 'playStreamRejected', + ({ data: { reason } }) => this.onPlayStreamRejected(reason) + ); + this.pixelStreaming.addEventListener( + 'loadFreezeFrame', + ({ data: { shouldShowPlayOverlay } }) => + this.onLoadFreezeFrame(shouldShowPlayOverlay) + ); + this.pixelStreaming.addEventListener( + 'statsReceived', + ({ data: { aggregatedStats } }) => + this.onStatsReceived(aggregatedStats) + ); + this.pixelStreaming.addEventListener( + 'latencyTestResult', + ({ data: { latencyTimings } }) => + this.onLatencyTestResults(latencyTimings) + ); + this.pixelStreaming.addEventListener( + 'streamerListMessage', + ({ data: { messageStreamerList, autoSelectedStreamerId } }) => + this.handleStreamerListMessage(messageStreamerList, autoSelectedStreamerId) + ); + this.pixelStreaming.addEventListener( + 'settingsChanged', + (event) => this.configUI.onSettingsChanged(event) + ); + } + + /** + * Gets the rootElement of the application, video stream and all UI are children of this element. + */ + public get rootElement(): HTMLElement { + if (!this._rootElement) { + this._rootElement = document.createElement('div'); + this._rootElement.id = 'playerUI'; + this._rootElement.classList.add('noselect'); + this._rootElement.appendChild( + this.pixelStreaming.videoElementParent + ); + this._rootElement.appendChild(this.uiFeaturesElement); + } + return this._rootElement; + } + + /** + * Gets the element that contains all the UI features, like the stats and settings panels. + */ + public get uiFeaturesElement(): HTMLElement { + if (!this._uiFeatureElement) { + this._uiFeatureElement = document.createElement('div'); + this._uiFeatureElement.id = 'uiFeatures'; + } + return this._uiFeatureElement; + } + + /** + * Shows the disconnect overlay + * @param updateText - the text that will be displayed in the overlay + */ + showDisconnectOverlay(updateText: string) { + this.hideCurrentOverlay(); + this.updateDisconnectOverlay(updateText); + this.disconnectOverlay.show(); + this.currentOverlay = this.disconnectOverlay; + } + + /** + * Update the disconnect overlays span text + * @param updateText - the new countdown number + */ + updateDisconnectOverlay(updateText: string) { + this.disconnectOverlay.update(updateText); + } + + /** + * Activates the disconnect overlays action + */ + onDisconnectionAction() { + this.disconnectOverlay.activate(); + } + + /** + * Hides the current overlay + */ + hideCurrentOverlay() { + if (this.currentOverlay != null) { + this.currentOverlay.hide(); + this.currentOverlay = null; + } + } + + /** + * Shows the connect overlay + */ + showConnectOverlay() { + this.hideCurrentOverlay(); + this.connectOverlay.show(); + this.currentOverlay = this.connectOverlay; + } + + /** + * Shows the play overlay + */ + showPlayOverlay() { + this.hideCurrentOverlay(); + this.playOverlay.show(); + this.currentOverlay = this.playOverlay; + } + + /** + * Shows the text overlay + * @param text - the text that will be shown in the overlay + */ + showTextOverlay(text: string) { + this.hideCurrentOverlay(); + this.infoOverlay.update(text); + this.infoOverlay.show(); + this.currentOverlay = this.infoOverlay; + } + + /** + * Shows the error overlay + * @param text - the text that will be shown in the overlay + */ + showErrorOverlay(text: string) { + this.hideCurrentOverlay(); + this.errorOverlay.update(text); + this.errorOverlay.show(); + this.currentOverlay = this.errorOverlay; + } + + /** + * Shows or hides the settings panel if clicked + */ + settingsClicked() { + this.statsPanel.hide(); + this.settingsPanel.toggleVisibility(); + } + + /** + * Shows or hides the stats panel if clicked + */ + statsClicked() { + this.settingsPanel.hide(); + this.statsPanel.toggleVisibility(); + } + + /** + * Activates the connect overlays action + */ + onConnectAction() { + this.connectOverlay.activate(); + } + + /** + * Activates the play overlays action + */ + onPlayAction() { + this.playOverlay.activate(); + } + + /** + * Shows the afk overlay + * @param countDown - the countdown number for the afk countdown + */ + showAfkOverlay(countDown: number, dismissAfk: () => void) { + this.hideCurrentOverlay(); + this.afkOverlay.updateCountdown(countDown); + this.afkOverlay.onAction(() => dismissAfk()); + this.afkOverlay.show(); + this.currentOverlay = this.afkOverlay; + } + + /** + * Show the Connect Overlay or auto connect + */ + showConnectOrAutoConnectOverlays() { + // set up if the auto play will be used or regular click to start + if (!this.pixelStreaming.config.isFlagEnabled(Flags.AutoConnect)) { + this.showConnectOverlay(); + } + } + + /** + * Show the webRtcAutoConnect Overlay and connect + */ + onWebRtcAutoConnect() { + this.showTextOverlay('Auto Connecting Now'); + } + + /** + * Set up functionality to happen when receiving a webRTC answer + */ + onWebRtcSdp() { + this.showTextOverlay('WebRTC Connection Negotiated'); + } + + /** + * Shows a text overlay to alert the user the stream is currently loading + */ + onStreamLoading() { + // build the spinner span + const spinnerSpan: HTMLSpanElement = document.createElement('span'); + spinnerSpan.className = 'visually-hidden'; + spinnerSpan.innerHTML = 'Loading...'; + + // build the spinner div + const spinnerDiv: HTMLDivElement = document.createElement('div'); + spinnerDiv.id = 'loading-spinner'; + spinnerDiv.className = 'spinner-border ms-2'; + spinnerDiv.setAttribute('role', 'status'); + + // append the spinner to the element + spinnerDiv.appendChild(spinnerSpan); + + this.showTextOverlay('Loading Stream ' + spinnerDiv.outerHTML); + } + + /** + * Event fired when the video is disconnected - displays the error overlay and resets the buttons stream tools upon disconnect + * @param eventString - the event text that will be shown in the overlay + */ + onDisconnect(eventString: string, showActionOrErrorOnDisconnect: boolean) { + if (showActionOrErrorOnDisconnect == false) { + this.showErrorOverlay(`Disconnected: ${eventString}`); + } else { + this.showDisconnectOverlay( + `Disconnected: ${eventString}
Click To Restart
` + ); + } + // disable starting a latency check + this.statsPanel.latencyTest.latencyTestButton.onclick = () => { + // do nothing + }; + } + + /** + * Handles when Web Rtc is connecting + */ + onWebRtcConnecting() { + this.showTextOverlay('Starting connection to server, please wait'); + } + + /** + * Handles when Web Rtc has connected + */ + onWebRtcConnected() { + this.showTextOverlay('WebRTC connected, waiting for video'); + } + + /** + * Handles when Web Rtc fails to connect + */ + onWebRtcFailed() { + this.showErrorOverlay('Unable to setup video'); + } + + onLoadFreezeFrame(shouldShowPlayOverlay: boolean) { + if (shouldShowPlayOverlay === true) { + Logger.Log(Logger.GetStackTrace(), 'showing play overlay'); + this.showPlayOverlay(); + } + } + + onPlayStream() { + this.hideCurrentOverlay(); + } + + onPlayStreamError(message: string) { + this.showErrorOverlay(message); + } + + onPlayStreamRejected(onRejectedReason: unknown) { + this.showPlayOverlay(); + } + + onVideoInitialized() { + if (!this.pixelStreaming.config.isFlagEnabled(Flags.AutoPlayVideo)) { + this.showPlayOverlay(); + } + + // starting a latency check + this.statsPanel.latencyTest.latencyTestButton.onclick = () => { + this.pixelStreaming.requestLatencyTest(); + }; + } + + /** + * Set up functionality to happen when calculating the average video encoder qp + * @param QP - the quality number of the stream + */ + onVideoEncoderAvgQP(QP: number) { + this.videoQpIndicator.updateQpTooltip(QP); + } + + onInitialSettings(settings: InitialSettings) { + if (settings.PixelStreamingSettings) { + const disableLatencyTest = + settings.PixelStreamingSettings.DisableLatencyTest; + if (disableLatencyTest) { + this.statsPanel.latencyTest.latencyTestButton.disabled = true; + this.statsPanel.latencyTest.latencyTestButton.title = + 'Disabled by -PixelStreamingDisableLatencyTester=true'; + Logger.Info( + Logger.GetStackTrace(), + '-PixelStreamingDisableLatencyTester=true, requesting latency report from the the browser to UE is disabled.' + ); + } + } + } + + onStatsReceived(aggregatedStats: AggregatedStats) { + // Grab all stats we can off the aggregated stats + this.statsPanel.handleStats(aggregatedStats); + } + + onLatencyTestResults(latencyTimings: LatencyTestResults) { + this.statsPanel.latencyTest.handleTestResult(latencyTimings); + } + + handleStreamerListMessage(messageStreamingList: MessageStreamerList, autoSelectedStreamerId: string | null) { + if (autoSelectedStreamerId === null) { + if(messageStreamingList.ids.length === 0) { + this.showDisconnectOverlay( + 'No streamers connected.
Click To Restart
' + ); + } else { + this.showTextOverlay( + 'Multiple streamers detected. Use the dropdown in the settings menu to select the streamer' + ); + } + } + } + + /** + * Update the players color variables + * @param isLightMode - should we use a light or dark color scheme + */ + updateColors(isLightMode: boolean) { + const rootElement = document.querySelector(':root') as HTMLElement; + if (isLightMode) { + rootElement.style.setProperty('--color0', '#e2e0dd80'); + rootElement.style.setProperty('--color1', '#FFFFFF'); + rootElement.style.setProperty('--color2', '#000000'); + rootElement.style.setProperty('--color3', '#0585fe'); + rootElement.style.setProperty('--color4', '#35b350'); + rootElement.style.setProperty('--color5', '#ffab00'); + rootElement.style.setProperty('--color6', '#e1e2dd'); + rootElement.style.setProperty('--color7', '#c3c4bf'); + } else { + rootElement.style.setProperty('--color0', '#1D1F2280'); + rootElement.style.setProperty('--color1', '#000000'); + rootElement.style.setProperty('--color2', '#FFFFFF'); + rootElement.style.setProperty('--color3', '#0585fe'); + rootElement.style.setProperty('--color4', '#35b350'); + rootElement.style.setProperty('--color5', '#ffab00'); + rootElement.style.setProperty('--color6', '#1e1d22'); + rootElement.style.setProperty('--color7', '#3c3b40'); + } + } +} diff --git a/Frontend/ui-library/src/Config/ConfigUI.ts b/Frontend/ui-library/src/Config/ConfigUI.ts new file mode 100644 index 00000000..43891f22 --- /dev/null +++ b/Frontend/ui-library/src/Config/ConfigUI.ts @@ -0,0 +1,433 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import { + Config, + FlagsIds, + NumericParametersIds, + OptionParametersIds, + TextParametersIds, + TextParameters, + OptionParameters, + Flags, + NumericParameters, + SettingsChangedEvent, + SettingFlag, + SettingNumber, + SettingText, + SettingOption, + Logger, + SettingBase +} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { SettingUIFlag } from './SettingUIFlag'; +import { SettingUINumber } from './SettingUINumber'; +import { SettingUIText } from './SettingUIText'; +import { SettingUIOption } from './SettingUIOption'; + +export const LightMode = 'LightMode' as const; +type ExtraFlags = typeof LightMode; +export type FlagsIdsExtended = FlagsIds | ExtraFlags; + +export class ConfigUI { + private customFlags = new Map< + FlagsIdsExtended, + SettingFlag + >(); + + /* A map of flags that can be toggled - options that can be set in the application - e.g. Use Mic? */ + private flagsUi = new Map< + FlagsIdsExtended, + SettingUIFlag + >(); + + /* A map of numerical settings - options that can be in the application - e.g. MinBitrate */ + private numericParametersUi = new Map< + NumericParametersIds, + SettingUINumber + >(); + + /* A map of text settings - e.g. signalling server url */ + private textParametersUi = new Map(); + + /* A map of enum based settings - e.g. preferred codec */ + private optionParametersUi = new Map< + OptionParametersIds, + SettingUIOption + >(); + + // ------------ Settings ----------------- + + constructor(config: Config) { + this.createCustomUISettings(config.useUrlParams); + this.registerSettingsUIComponents(config); + } + + /** + * Create custom UI settings that are not provided by the Pixel Streaming library. + */ + createCustomUISettings(useUrlParams: boolean) { + this.customFlags.set( + LightMode, + new SettingFlag( + LightMode, + 'Color Scheme: Dark Mode', + 'Page styling will be either light or dark', + false /*if want to use system pref: (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches)*/, + useUrlParams, + (isLightMode: boolean, setting: SettingBase) => { + setting.label = `Color Scheme: ${isLightMode ? 'Light' : 'Dark'} Mode`; + } + ) + ); + } + + /** + * Creates UI wrapper components for each setting element in config. + * @param config + */ + registerSettingsUIComponents(config: Config) { + for (const setting of config.getFlags()) { + this.flagsUi.set(setting.id, new SettingUIFlag(setting)); + } + for (const setting of Array.from(this.customFlags.values())) { + this.flagsUi.set( + setting.id, + new SettingUIFlag(setting) + ); + } + for (const setting of config.getTextSettings()) { + this.textParametersUi.set(setting.id, new SettingUIText(setting)); + } + for (const setting of config.getNumericSettings()) { + this.numericParametersUi.set( + setting.id, + new SettingUINumber(setting) + ); + } + for (const setting of config.getOptionSettings()) { + this.optionParametersUi.set( + setting.id, + new SettingUIOption(setting) + ); + } + } + + /** + * Make DOM elements for a settings section with a heading. + * @param settingsElem The parent container for our DOM elements. + * @param sectionHeading The heading element to go into the section. + * @returns The constructed DOM element for the section. + */ + buildSectionWithHeading(settingsElem: HTMLElement, sectionHeading: string) { + // make section element + const sectionElem = document.createElement('section'); + sectionElem.classList.add('settingsContainer'); + + // make section heading + const psSettingsHeader = document.createElement('div'); + psSettingsHeader.classList.add('settingsHeader'); + psSettingsHeader.classList.add('settings-text'); + psSettingsHeader.textContent = sectionHeading; + + // add section and heading to parent settings element + sectionElem.appendChild(psSettingsHeader); + settingsElem.appendChild(sectionElem); + return sectionElem; + } + + /** + * Setup flags with their default values and add them to the `Config.flags` map. + * @param settingsElem - The element that contains all the individual settings sections, flags, and so on. + */ + populateSettingsElement(settingsElem: HTMLElement): void { + /* Setup all Pixel Streaming specific settings */ + const psSettingsSection = this.buildSectionWithHeading( + settingsElem, + 'Pixel Streaming' + ); + + // make settings show up in DOM + this.addSettingText( + psSettingsSection, + this.textParametersUi.get(TextParameters.SignallingServerUrl) + ); + this.addSettingOption( + psSettingsSection, + this.optionParametersUi.get(OptionParameters.StreamerId) + ); + this.addSettingFlag( + psSettingsSection, + this.flagsUi.get(Flags.AutoConnect) + ); + this.addSettingFlag( + psSettingsSection, + this.flagsUi.get(Flags.AutoPlayVideo) + ); + this.addSettingFlag( + psSettingsSection, + this.flagsUi.get(Flags.BrowserSendOffer) + ); + this.addSettingFlag( + psSettingsSection, + this.flagsUi.get(Flags.UseMic) + ); + this.addSettingFlag( + psSettingsSection, + this.flagsUi.get(Flags.StartVideoMuted) + ); + this.addSettingFlag( + psSettingsSection, + this.flagsUi.get(Flags.PreferSFU) + ); + this.addSettingFlag( + psSettingsSection, + this.flagsUi.get(Flags.IsQualityController) + ); + this.addSettingFlag( + psSettingsSection, + this.flagsUi.get(Flags.ForceMonoAudio) + ); + this.addSettingFlag( + psSettingsSection, + this.flagsUi.get(Flags.ForceTURN) + ); + this.addSettingFlag( + psSettingsSection, + this.flagsUi.get(Flags.SuppressBrowserKeys) + ); + this.addSettingFlag( + psSettingsSection, + this.flagsUi.get(Flags.AFKDetection) + ); + this.addSettingNumeric( + psSettingsSection, + this.numericParametersUi.get(NumericParameters.AFKTimeoutSecs) + ); + + /* Setup all view/ui related settings under this section */ + const viewSettingsSection = this.buildSectionWithHeading( + settingsElem, + 'UI' + ); + this.addSettingFlag( + viewSettingsSection, + this.flagsUi.get(Flags.MatchViewportResolution) + ); + + this.addSettingFlag( + viewSettingsSection, + this.flagsUi.get(Flags.HoveringMouseMode) + ); + + this.addSettingFlag(viewSettingsSection, this.flagsUi.get(LightMode)); + + /* Setup all encoder related settings under this section */ + const encoderSettingsSection = this.buildSectionWithHeading( + settingsElem, + 'Encoder' + ); + + this.addSettingNumeric( + encoderSettingsSection, + this.numericParametersUi.get(NumericParameters.MinQP) + ); + this.addSettingNumeric( + encoderSettingsSection, + this.numericParametersUi.get(NumericParameters.MaxQP) + ); + + const preferredCodecOption = this.optionParametersUi.get( + OptionParameters.PreferredCodec + ); + this.addSettingOption( + encoderSettingsSection, + this.optionParametersUi.get(OptionParameters.PreferredCodec) + ); + if ( + preferredCodecOption && + [...preferredCodecOption.selector.options] + .map((o) => o.value) + .includes('Only available on Chrome') + ) { + preferredCodecOption.disable(); + } + + /* Setup all webrtc related settings under this section */ + const webrtcSettingsSection = this.buildSectionWithHeading( + settingsElem, + 'WebRTC' + ); + + this.addSettingNumeric( + webrtcSettingsSection, + this.numericParametersUi.get(NumericParameters.WebRTCFPS) + ); + this.addSettingNumeric( + webrtcSettingsSection, + this.numericParametersUi.get(NumericParameters.WebRTCMinBitrate) + ); + this.addSettingNumeric( + webrtcSettingsSection, + this.numericParametersUi.get(NumericParameters.WebRTCMaxBitrate) + ); + } + + /** + * Add a SettingText element to a particular settings section in the DOM and registers that text in the text settings map. + * @param settingsSection The settings section HTML element. + * @param settingText The textual settings object. + */ + addSettingText( + settingsSection: HTMLElement, + settingText?: SettingUIText + ): void { + if (settingText) { + settingsSection.appendChild(settingText.rootElement); + this.textParametersUi.set(settingText.setting.id, settingText); + } + } + + /** + * Add a SettingFlag element to a particular settings section in the DOM and registers that flag in the Config.flag map. + * @param settingsSection The settings section HTML element. + * @param settingFlag The settings flag object. + */ + addSettingFlag( + settingsSection: HTMLElement, + settingFlag?: SettingUIFlag + ): void { + if (settingFlag) { + settingsSection.appendChild(settingFlag.rootElement); + this.flagsUi.set(settingFlag.setting.id, settingFlag); + } + } + + /** + * Add a numeric setting element to a particular settings section in the DOM and registers that flag in the Config.numericParameters map. + * @param settingsSection The settings section HTML element. + * @param settingFlag The settings flag object. + */ + addSettingNumeric( + settingsSection: HTMLElement, + setting?: SettingUINumber + ): void { + if (setting) { + settingsSection.appendChild(setting.rootElement); + this.numericParametersUi.set(setting.setting.id, setting); + } + } + + /** + * Add an enum based settings element to a particular settings section in the DOM and registers that flag in the Config.enumParameters map. + * @param settingsSection The settings section HTML element. + * @param settingFlag The settings flag object. + */ + addSettingOption( + settingsSection: HTMLElement, + setting?: SettingUIOption + ): void { + if (setting) { + settingsSection.appendChild(setting.rootElement); + this.optionParametersUi.set(setting.setting.id, setting); + } + } + + onSettingsChanged({ data: { id, target, type } }: SettingsChangedEvent) { + if (type === 'flag') { + const _id = id as FlagsIds; + const _target = target as SettingFlag; + const setting = this.flagsUi.get(_id); + if (setting) { + if (setting.flag !== _target.flag) { + setting.flag = _target.flag; + } + if (setting.label !== _target.label) { + setting.label = _target.label; + } + } + } else if (type === 'number') { + const _id = id as NumericParametersIds; + const _target = target as SettingNumber; + const setting = this.numericParametersUi.get(_id); + if (setting) { + if (setting.number !== _target.number) { + setting.number = _target.number; + } + if (setting.label !== _target.label) { + setting.label = _target.label; + } + } + } else if (type === 'text') { + const _id = id as TextParametersIds; + const _target = target as SettingText; + const setting = this.textParametersUi.get(_id); + if (setting) { + if (setting.text !== _target.text) { + setting.text = _target.text; + } + if (setting.label !== _target.label) { + setting.label = _target.label; + } + } + } else if (type === 'option') { + const _id = id as OptionParametersIds; + const _target = target as SettingOption; + const setting = this.optionParametersUi.get(_id); + if (setting) { + const uiOptions = setting.options; + const targetOptions = _target.options; + if ( + uiOptions.length !== targetOptions.length || + !uiOptions.every((value) => targetOptions.includes(value)) + ) { + setting.options = _target.options; + } + if (setting.selected !== _target.selected) { + setting.selected = _target.selected; + } + if (setting.label !== _target.label) { + setting.label = _target.label; + } + } + } + } + + /** + * Add a callback to fire when the flag is toggled. + * @param id The id of the flag. + * @param onChangeListener The callback to fire when the value changes. + */ + addCustomFlagOnSettingChangedListener( + id: ExtraFlags, + onChangeListener: (newFlagValue: boolean) => void + ): void { + if (this.customFlags.has(id)) { + this.customFlags.get(id).onChange = onChangeListener; + } + } + + /** + * Set the label for the flag. + * @param id The id of the flag. + * @param label The new label to use for the flag. + */ + setCustomFlagLabel(id: ExtraFlags, label: string) { + if (!this.customFlags.has(id)) { + Logger.Warning( + Logger.GetStackTrace(), + `Cannot set label for flag called ${id} - it does not exist in the Config.flags map.` + ); + } else { + this.customFlags.get(id).label = label; + this.flagsUi.get(id).label = label; + } + } + + /** + * Get the value of the configuration flag which has the given id. + * @param id The unique id for the flag. + * @returns True if the flag is enabled. + */ + isCustomFlagEnabled(id: ExtraFlags): boolean { + return this.customFlags.get(id).flag as boolean; + } +} diff --git a/Frontend/ui-library/src/Config/SettingUIBase.ts b/Frontend/ui-library/src/Config/SettingUIBase.ts new file mode 100644 index 00000000..c2c19fbe --- /dev/null +++ b/Frontend/ui-library/src/Config/SettingUIBase.ts @@ -0,0 +1,32 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import { SettingBase } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; + +/** + * Base class for a setting that has a text label, an arbitrary setting value it stores, an a HTML element that represents this setting. + */ +export class SettingUIBase { + _setting: SettingBase; + _rootElement: HTMLElement; + + constructor(setting: SettingBase) { + this._setting = setting; + } + + /** + * @returns The setting component. + */ + public get setting(): SettingBase { + return this._setting; + } + + /** + * @returns Return or creates a HTML element that represents this setting in the DOM. + */ + public get rootElement(): HTMLElement { + if (!this._rootElement) { + this._rootElement = document.createElement('div'); + } + return this._rootElement; + } +} diff --git a/Frontend/ui-library/src/Config/SettingUIFlag.ts b/Frontend/ui-library/src/Config/SettingUIFlag.ts new file mode 100644 index 00000000..98dbcc8a --- /dev/null +++ b/Frontend/ui-library/src/Config/SettingUIFlag.ts @@ -0,0 +1,118 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import type { + FlagsIds, + SettingFlag +} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { SettingUIBase } from './SettingUIBase'; + +export class SettingUIFlag< + CustomIds extends string = FlagsIds +> extends SettingUIBase { + /* We toggle this checkbox to reflect the value of our setting's boolean flag. */ + _checkbox: HTMLInputElement; // input type="checkbox" + + /* This element contains a text node that reflects the setting's text label. */ + _settingsTextElem: HTMLElement; + + onChangeEmit: (changedValue: boolean) => void; + + constructor(setting: SettingFlag) { + super(setting); + + this.label = setting.label; + this.flag = setting.flag; + } + + /** + * @returns The setting component. + */ + public get setting(): SettingFlag { + return this._setting as SettingFlag; + } + + public get settingsTextElem(): HTMLElement { + if (!this._settingsTextElem) { + this._settingsTextElem = document.createElement('div'); + this._settingsTextElem.innerText = this.setting._label; + this._settingsTextElem.title = this.setting.description; + } + return this._settingsTextElem; + } + + public get checkbox(): HTMLInputElement { + if (!this._checkbox) { + this._checkbox = document.createElement('input'); + this._checkbox.type = 'checkbox'; + } + return this._checkbox; + } + + /** + * @returns Return or creates a HTML element that represents this setting in the DOM. + */ + public get rootElement(): HTMLElement { + if (!this._rootElement) { + // create root div with "setting" css class + this._rootElement = document.createElement('div'); + this._rootElement.id = this.setting.id; + this._rootElement.classList.add('setting'); + + // create div element to contain our setting's text + this._rootElement.appendChild(this.settingsTextElem); + + // create label element to wrap out input type + const wrapperLabel = document.createElement('label'); + wrapperLabel.classList.add('tgl-switch'); + this._rootElement.appendChild(wrapperLabel); + + // create input type=checkbox + this.checkbox.title = this.setting.description; + this.checkbox.classList.add('tgl'); + this.checkbox.classList.add('tgl-flat'); + const slider = document.createElement('div'); + slider.classList.add('tgl-slider'); + wrapperLabel.appendChild(this.checkbox); + wrapperLabel.appendChild(slider); + + // setup on change from checkbox + this.checkbox.addEventListener('change', () => { + if (this.setting.flag !== this.checkbox.checked) { + this.setting.flag = this.checkbox.checked; + this.setting.updateURLParams(); + } + }); + } + return this._rootElement; + } + + /** + * Update the setting's stored value. + * @param inValue The new value for the setting. + */ + public set flag(inValue: boolean) { + this.checkbox.checked = inValue; + } + + /** + * Get value + */ + public get flag() { + return this.checkbox.checked; + } + + /** + * Set the label text for the setting. + * @param label setting label. + */ + public set label(inLabel: string) { + this.settingsTextElem.innerText = inLabel; + } + + /** + * Get label + */ + public get label() { + return this.settingsTextElem.innerText; + } +} diff --git a/Frontend/ui-library/src/Config/SettingUINumber.ts b/Frontend/ui-library/src/Config/SettingUINumber.ts new file mode 100644 index 00000000..db171f5e --- /dev/null +++ b/Frontend/ui-library/src/Config/SettingUINumber.ts @@ -0,0 +1,129 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import type { + NumericParametersIds, + SettingNumber +} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { SettingUIBase } from './SettingUIBase'; + +/** + * A number spinner with a text label beside it. + */ +export class SettingUINumber< + CustomIds extends string = NumericParametersIds +> extends SettingUIBase { + _spinner: HTMLInputElement; + + /* This element contains a text node that reflects the setting's text label. */ + _settingsTextElem: HTMLElement; + + constructor(setting: SettingNumber) { + super(setting); + + this.label = this.setting.label; + this.number = this.setting.number; + } + + /** + * @returns The setting component. + */ + public get setting(): SettingNumber { + return this._setting as SettingNumber; + } + + public get settingsTextElem(): HTMLElement { + if (!this._settingsTextElem) { + this._settingsTextElem = document.createElement('label'); + this._settingsTextElem.innerText = this.setting.label; + this._settingsTextElem.title = this.setting.description; + } + return this._settingsTextElem; + } + + /** + * Get the HTMLInputElement for the button. + */ + public get spinner(): HTMLInputElement { + if (!this._spinner) { + this._spinner = document.createElement('input'); + this._spinner.type = 'number'; + this._spinner.min = this.setting.min.toString(); + this._spinner.max = this.setting.max.toString(); + this._spinner.value = this.setting.number.toString(); + this._spinner.title = this.setting.description; + this._spinner.classList.add('form-control'); + } + return this._spinner; + } + + /** + * @returns Return or creates a HTML element that represents this setting in the DOM. + */ + public get rootElement(): HTMLElement { + if (!this._rootElement) { + // create root div with "setting" css class + this._rootElement = document.createElement('div'); + this._rootElement.classList.add('setting'); + this._rootElement.classList.add('form-group'); + + // create div element to contain our setting's text + this._rootElement.appendChild(this.settingsTextElem); + + // create label element to wrap out input type + this._rootElement.appendChild(this.spinner); + + // setup onchange + this.spinner.onchange = (event: Event) => { + const inputElem = event.target as HTMLInputElement; + + const parsedValue = Number.parseInt(inputElem.value); + + if (Number.isNaN(parsedValue)) { + Logger.Warning( + Logger.GetStackTrace(), + `Could not parse value change into a valid number - value was ${inputElem.value}, resetting value to ${this.setting.min}` + ); + if (this.setting.number !== this.setting.min) { + this.setting.number = this.setting.min; + } + } else { + if (this.setting.number !== parsedValue) { + this.setting.number = parsedValue; + this.setting.updateURLParams(); + } + } + }; + } + return this._rootElement; + } + + /** + * Set the number in the spinner (will be clamped within range). + */ + public set number(newNumber: number) { + this.spinner.value = this.setting.clamp(newNumber).toString(); + } + + /** + * Get value + */ + public get number() { + return +this.spinner.value; + } + + /** + * Set the label text for the setting. + * @param label setting label. + */ + public set label(inLabel: string) { + this.settingsTextElem.innerText = inLabel; + } + + /** + * Get label + */ + public get label() { + return this.settingsTextElem.innerText; + } +} diff --git a/Frontend/ui-library/src/Config/SettingUIOption.ts b/Frontend/ui-library/src/Config/SettingUIOption.ts new file mode 100644 index 00000000..03500a39 --- /dev/null +++ b/Frontend/ui-library/src/Config/SettingUIOption.ts @@ -0,0 +1,138 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import type { + OptionParametersIds, + SettingOption +} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { SettingUIBase } from './SettingUIBase'; + +export class SettingUIOption< + CustomIds extends string = OptionParametersIds +> extends SettingUIBase { + /* A select element that reflects the value of this setting. */ + _selector: HTMLSelectElement; // + + /* This element contains a text node that reflects the setting's text label. */ + _settingsTextElem: HTMLElement; + + constructor(setting: SettingOption) { + super(setting); + + this.label = this.setting.label; + this.options = this.setting.options; + this.selected = this.setting.selected; + } + + /** + * @returns The setting component. + */ + public get setting(): SettingOption { + return this._setting as SettingOption; + } + + public get selector(): HTMLSelectElement { + if (!this._selector) { + this._selector = document.createElement('select'); + this._selector.classList.add('form-control'); + this._selector.classList.add('settings-option'); + } + return this._selector; + } + + public get settingsTextElem(): HTMLElement { + if (!this._settingsTextElem) { + this._settingsTextElem = document.createElement('div'); + this._settingsTextElem.innerText = this.setting.label; + this._settingsTextElem.title = this.setting.description; + } + return this._settingsTextElem; + } + + /** + * Set the label text for the setting. + * @param label setting label. + */ + public set label(inLabel: string) { + this.settingsTextElem.innerText = inLabel; + } + + /** + * Get label + */ + public get label() { + return this.settingsTextElem.innerText; + } + + /** + * @returns Return or creates a HTML element that represents this setting in the DOM. + */ + public get rootElement(): HTMLElement { + if (!this._rootElement) { + // create root div with "setting" css class + this._rootElement = document.createElement('div'); + this._rootElement.id = this.setting.id; + this._rootElement.classList.add('setting'); + this._rootElement.classList.add('form-group'); + + // create div element to contain our setting's text + this._rootElement.appendChild(this.settingsTextElem); + + // create label element to wrap out input type + const wrapperLabel = document.createElement('label'); + this._rootElement.appendChild(wrapperLabel); + + // create select element + this.selector.title = this.setting.description; + wrapperLabel.appendChild(this.selector); + + // setup on change from selector + this.selector.onchange = () => { + if (this.setting.selected !== this.selector.value) { + this.setting.selected = this.selector.value; + this.setting.updateURLParams(); + } + }; + } + return this._rootElement; + } + + public set options(values: Array) { + for (let i = this.selector.options.length - 1; i >= 0; i--) { + this.selector.remove(i); + } + + values.forEach((value: string) => { + const opt = document.createElement('option'); + opt.value = value; + opt.innerHTML = value; + this.selector.appendChild(opt); + }); + } + + public get options() { + return [...this.selector.options].map((o) => o.value); + } + + public set selected(value: string) { + // A user may not specify the full possible value so we instead use the closest match. + // eg ?xxx=H264 would select 'H264 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f' + const filteredList = this.options.filter( + (option: string) => option.indexOf(value) !== -1 + ); + if (filteredList.length) { + this.selector.value = filteredList[0]; + } + } + + public get selected() { + return this.selector.value; + } + + public disable() { + this.selector.disabled = true; + } + + public enable() { + this.selector.disabled = false; + } +} diff --git a/Frontend/ui-library/src/Config/SettingUIText.ts b/Frontend/ui-library/src/Config/SettingUIText.ts new file mode 100644 index 00000000..325dcebb --- /dev/null +++ b/Frontend/ui-library/src/Config/SettingUIText.ts @@ -0,0 +1,111 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import type { + SettingText, + TextParametersIds +} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { SettingUIBase } from './SettingUIBase'; + +export class SettingUIText< + CustomIds extends string = TextParametersIds +> extends SettingUIBase { + /* A text box that reflects the value of this setting. */ + _textbox: HTMLInputElement; // input type="text" + + /* This element contains a text node that reflects the setting's text label. */ + _settingsTextElem: HTMLElement; + + constructor(setting: SettingText) { + super(setting); + + this.label = this.setting.label; + this.text = this.setting.text; + } + + /** + * @returns The setting component. + */ + public get setting(): SettingText { + return this._setting as SettingText; + } + + public get settingsTextElem(): HTMLElement { + if (!this._settingsTextElem) { + this._settingsTextElem = document.createElement('div'); + this._settingsTextElem.innerText = this.setting.label; + this._settingsTextElem.title = this.setting.description; + } + return this._settingsTextElem; + } + + public get textbox(): HTMLInputElement { + if (!this._textbox) { + this._textbox = document.createElement('input'); + this._textbox.classList.add('form-control'); + this._textbox.type = 'textbox'; + } + return this._textbox; + } + + /** + * @returns Return or creates a HTML element that represents this setting in the DOM. + */ + public get rootElement(): HTMLElement { + if (!this._rootElement) { + // create root div with "setting" css class + this._rootElement = document.createElement('div'); + this._rootElement.id = this.setting.id; + this._rootElement.classList.add('setting'); + + // create div element to contain our setting's text + this._rootElement.appendChild(this.settingsTextElem); + + // create label element to wrap out input type + const wrapperLabel = document.createElement('label'); + this._rootElement.appendChild(wrapperLabel); + + // create input type=checkbox + this.textbox.title = this.setting.description; + wrapperLabel.appendChild(this.textbox); + + // setup on change from checkbox + this.textbox.addEventListener('input', () => { + if (this.setting.text !== this.textbox.value) { + this.setting.text = this.textbox.value; + this.setting.updateURLParams(); + } + }); + } + return this._rootElement; + } + + /** + * Update the setting's stored value. + * @param inValue The new value for the setting. + */ + public set text(inValue: string) { + this.textbox.value = inValue; + } + + /** + * Get value + */ + public get text() { + return this.textbox.value; + } + + /** + * Set the label text for the setting. + * @param label setting label. + */ + public set label(inLabel: string) { + this.settingsTextElem.innerText = inLabel; + } + + /** + * Get label + */ + public get label() { + return this.settingsTextElem.innerText; + } +} diff --git a/Frontend/library/src/AFK/AFKOverlay.ts b/Frontend/ui-library/src/Overlay/AFKOverlay.ts similarity index 97% rename from Frontend/library/src/AFK/AFKOverlay.ts rename to Frontend/ui-library/src/Overlay/AFKOverlay.ts index 78476025..3fbfefd9 100644 --- a/Frontend/library/src/AFK/AFKOverlay.ts +++ b/Frontend/ui-library/src/Overlay/AFKOverlay.ts @@ -1,6 +1,6 @@ // Copyright Epic Games, Inc. All Rights Reserved. -import { ActionOverlay } from '../Overlay/ActionOverlay'; +import { ActionOverlay } from './ActionOverlay'; /** * Show an overlay for when the session is unattended, it begins a countdown timer, which when elapsed will disconnect the stream. diff --git a/Frontend/library/src/Overlay/ActionOverlay.ts b/Frontend/ui-library/src/Overlay/ActionOverlay.ts similarity index 95% rename from Frontend/library/src/Overlay/ActionOverlay.ts rename to Frontend/ui-library/src/Overlay/ActionOverlay.ts index 40717918..d321b091 100644 --- a/Frontend/library/src/Overlay/ActionOverlay.ts +++ b/Frontend/ui-library/src/Overlay/ActionOverlay.ts @@ -1,6 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. -import { Logger } from '../Logger/Logger'; +import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; + import { OverlayBase } from './BaseOverlay'; /** diff --git a/Frontend/library/src/Overlay/BaseOverlay.ts b/Frontend/ui-library/src/Overlay/BaseOverlay.ts similarity index 100% rename from Frontend/library/src/Overlay/BaseOverlay.ts rename to Frontend/ui-library/src/Overlay/BaseOverlay.ts diff --git a/Frontend/library/src/Overlay/ConnectOverlay.ts b/Frontend/ui-library/src/Overlay/ConnectOverlay.ts similarity index 100% rename from Frontend/library/src/Overlay/ConnectOverlay.ts rename to Frontend/ui-library/src/Overlay/ConnectOverlay.ts diff --git a/Frontend/library/src/Overlay/DisconnectOverlay.ts b/Frontend/ui-library/src/Overlay/DisconnectOverlay.ts similarity index 100% rename from Frontend/library/src/Overlay/DisconnectOverlay.ts rename to Frontend/ui-library/src/Overlay/DisconnectOverlay.ts diff --git a/Frontend/library/src/Overlay/ErrorOverlay.ts b/Frontend/ui-library/src/Overlay/ErrorOverlay.ts similarity index 100% rename from Frontend/library/src/Overlay/ErrorOverlay.ts rename to Frontend/ui-library/src/Overlay/ErrorOverlay.ts diff --git a/Frontend/library/src/Overlay/InfoOverlay.ts b/Frontend/ui-library/src/Overlay/InfoOverlay.ts similarity index 100% rename from Frontend/library/src/Overlay/InfoOverlay.ts rename to Frontend/ui-library/src/Overlay/InfoOverlay.ts diff --git a/Frontend/library/src/Overlay/PlayOverlay.ts b/Frontend/ui-library/src/Overlay/PlayOverlay.ts similarity index 100% rename from Frontend/library/src/Overlay/PlayOverlay.ts rename to Frontend/ui-library/src/Overlay/PlayOverlay.ts diff --git a/Frontend/library/src/Overlay/TextOverlay.ts b/Frontend/ui-library/src/Overlay/TextOverlay.ts similarity index 100% rename from Frontend/library/src/Overlay/TextOverlay.ts rename to Frontend/ui-library/src/Overlay/TextOverlay.ts diff --git a/Frontend/library/src/Application/PixelStreamingApplicationStyles.ts b/Frontend/ui-library/src/Styles/PixelStreamingApplicationStyles.ts similarity index 98% rename from Frontend/library/src/Application/PixelStreamingApplicationStyles.ts rename to Frontend/ui-library/src/Styles/PixelStreamingApplicationStyles.ts index 770afad9..538c6ba7 100644 --- a/Frontend/library/src/Application/PixelStreamingApplicationStyles.ts +++ b/Frontend/ui-library/src/Styles/PixelStreamingApplicationStyles.ts @@ -267,6 +267,11 @@ export class PixelStreamingApplicationStyle { verticalAlign: 'middle', fontWeight: 'normal' }, + '.settings-option': { + width: '100%', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' + }, '#connectOverlay, #playOverlay, #infoOverlay, #errorOverlay, #afkOverlay, #disconnectOverlay': { zIndex: '30', @@ -410,7 +415,7 @@ export class PixelStreamingApplicationStyle { '.form-group': { paddingTop: '4px', display: 'grid', - gridTemplateColumns: '50% 50%', + gridTemplateColumns: '80% 20%', rowGap: '4px', paddingRight: '10px', paddingLeft: '10px' diff --git a/Frontend/library/src/UI/Controls.ts b/Frontend/ui-library/src/UI/Controls.ts similarity index 94% rename from Frontend/library/src/UI/Controls.ts rename to Frontend/ui-library/src/UI/Controls.ts index 295d3320..9693be2d 100644 --- a/Frontend/library/src/UI/Controls.ts +++ b/Frontend/ui-library/src/UI/Controls.ts @@ -4,7 +4,7 @@ import { FullScreenIcon } from './FullscreenIcon'; import { SettingsIcon } from './SettingsIcon'; import { StatsIcon } from './StatsIcon'; import { XRIcon } from './XRIcon'; -import { WebXRController } from '../WebXR/WebXRController'; +import { WebXRController } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; /** * Element containing various controls like stats, settings, fullscreen. diff --git a/Frontend/library/src/UI/FullscreenIcon.ts b/Frontend/ui-library/src/UI/FullscreenIcon.ts similarity index 100% rename from Frontend/library/src/UI/FullscreenIcon.ts rename to Frontend/ui-library/src/UI/FullscreenIcon.ts diff --git a/Frontend/library/src/UI/LabelledButton.ts b/Frontend/ui-library/src/UI/LabelledButton.ts similarity index 100% rename from Frontend/library/src/UI/LabelledButton.ts rename to Frontend/ui-library/src/UI/LabelledButton.ts diff --git a/Frontend/library/src/UI/LatencyTest.ts b/Frontend/ui-library/src/UI/LatencyTest.ts similarity index 96% rename from Frontend/library/src/UI/LatencyTest.ts rename to Frontend/ui-library/src/UI/LatencyTest.ts index b507f1b6..8bb22de7 100644 --- a/Frontend/library/src/UI/LatencyTest.ts +++ b/Frontend/ui-library/src/UI/LatencyTest.ts @@ -1,7 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. -import { LatencyTestResults } from '../DataChannel/LatencyTestResults'; -import { Logger } from '../Logger/Logger'; +import { LatencyTestResults } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; /** * Latency test UI elements and results handling. diff --git a/Frontend/library/src/UI/SettingsIcon.ts b/Frontend/ui-library/src/UI/SettingsIcon.ts similarity index 100% rename from Frontend/library/src/UI/SettingsIcon.ts rename to Frontend/ui-library/src/UI/SettingsIcon.ts diff --git a/Frontend/library/src/UI/SettingsPanel.ts b/Frontend/ui-library/src/UI/SettingsPanel.ts similarity index 100% rename from Frontend/library/src/UI/SettingsPanel.ts rename to Frontend/ui-library/src/UI/SettingsPanel.ts diff --git a/Frontend/library/src/UI/StatsIcon.ts b/Frontend/ui-library/src/UI/StatsIcon.ts similarity index 100% rename from Frontend/library/src/UI/StatsIcon.ts rename to Frontend/ui-library/src/UI/StatsIcon.ts diff --git a/Frontend/library/src/UI/StatsPanel.ts b/Frontend/ui-library/src/UI/StatsPanel.ts similarity index 91% rename from Frontend/library/src/UI/StatsPanel.ts rename to Frontend/ui-library/src/UI/StatsPanel.ts index 5b3a26e6..a93cdc19 100644 --- a/Frontend/library/src/UI/StatsPanel.ts +++ b/Frontend/ui-library/src/UI/StatsPanel.ts @@ -1,8 +1,8 @@ // Copyright Epic Games, Inc. All Rights Reserved. import { LatencyTest } from './LatencyTest'; -import { Logger } from '../Logger/Logger'; -import { AggregatedStats } from '../PeerConnectionController/AggregatedStats'; +import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; +import { AggregatedStats } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2'; import { MathUtils } from '../Util/MathUtils'; /** @@ -31,7 +31,6 @@ export class StatsPanel { statsMap = new Map(); constructor() { - this._rootElement = null; this.latencyTest = new LatencyTest(); } @@ -239,7 +238,7 @@ export class StatsPanel { this.addOrUpdateStat( 'FramesDroppedStat', 'Frames dropped', - stats.inboundVideoStats.framesDropped.toString() + stats.inboundVideoStats.framesDropped?.toString() ); if (stats.inboundVideoStats.codecId) { @@ -247,7 +246,9 @@ export class StatsPanel { 'VideoCodecStat', 'Video codec', // Split the codec to remove the Fmtp line - stats.codecs.get(stats.inboundVideoStats.codecId).split(' ')[0] + stats.codecs + .get(stats.inboundVideoStats.codecId) + ?.split(' ')[0] ?? '' ); } @@ -256,7 +257,9 @@ export class StatsPanel { 'AudioCodecStat', 'Audio codec', // Split the codec to remove the Fmtp line - stats.codecs.get(stats.inboundAudioStats.codecId).split(' ')[0] + stats.codecs + .get(stats.inboundAudioStats.codecId) + ?.split(' ')[0] ?? '' ); } @@ -272,6 +275,25 @@ export class StatsPanel { : "Can't calculate"; this.addOrUpdateStat('RTTStat', 'Net RTT (ms)', netRTT); + this.addOrUpdateStat( + 'DurationStat', + 'Duration', + stats.sessionStats.runTime + ); + + this.addOrUpdateStat( + 'ControlsInputStat', + 'Controls stream input', + stats.sessionStats.controlsStreamInput + ); + + // QP + this.addOrUpdateStat( + 'QPStat', + 'Video quantization parameter', + stats.sessionStats.videoEncoderAvgQP.toString() + ); + // todo: //statsText += `
Browser receive to composite (ms): ${stats.inboundVideoStats.receiveToCompositeMs}
`; diff --git a/Frontend/library/src/UI/VideoQpIndicator.ts b/Frontend/ui-library/src/UI/VideoQpIndicator.ts similarity index 100% rename from Frontend/library/src/UI/VideoQpIndicator.ts rename to Frontend/ui-library/src/UI/VideoQpIndicator.ts diff --git a/Frontend/library/src/UI/XRIcon.ts b/Frontend/ui-library/src/UI/XRIcon.ts similarity index 100% rename from Frontend/library/src/UI/XRIcon.ts rename to Frontend/ui-library/src/UI/XRIcon.ts diff --git a/Frontend/library/src/Util/MathUtils.ts b/Frontend/ui-library/src/Util/MathUtils.ts similarity index 100% rename from Frontend/library/src/Util/MathUtils.ts rename to Frontend/ui-library/src/Util/MathUtils.ts diff --git a/Frontend/ui-library/src/pixelstreamingfrontend-ui.ts b/Frontend/ui-library/src/pixelstreamingfrontend-ui.ts new file mode 100644 index 00000000..e9b1ced0 --- /dev/null +++ b/Frontend/ui-library/src/pixelstreamingfrontend-ui.ts @@ -0,0 +1,16 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +export { Application } from './Application/Application'; + +export { PixelStreamingApplicationStyle } from './Styles/PixelStreamingApplicationStyles'; + +export { AFKOverlay } from './Overlay/AFKOverlay'; +export { ActionOverlay } from './Overlay/ActionOverlay'; +export { OverlayBase } from './Overlay/BaseOverlay'; +export { ConnectOverlay } from './Overlay/ConnectOverlay'; +export { DisconnectOverlay } from './Overlay/DisconnectOverlay'; +export { ErrorOverlay } from './Overlay/ErrorOverlay'; +export { InfoOverlay } from './Overlay/InfoOverlay'; +export { PlayOverlay } from './Overlay/PlayOverlay'; +export { TextOverlay } from './Overlay/TextOverlay'; +export { ConfigUI } from './Config/ConfigUI'; diff --git a/Frontend/ui-library/tsconfig.json b/Frontend/ui-library/tsconfig.json new file mode 100644 index 00000000..372cefd6 --- /dev/null +++ b/Frontend/ui-library/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "outDir": "./types", + "noImplicitAny": true, + "module": "es6", + "esModuleInterop": true, + "target": "ES6", + "moduleResolution": "node", + "sourceMap": true, + "allowJs": true, + "declaration": true + }, + "lib": ["es2015"], + "include": ["./src/*.ts"], + "typedocOptions": { + "exclude": "src/index.*", + "entryPoints": ["src/pixelstreamingfrontend-ui.ts"], + "sort": ["enum-value-ascending", "required-first", "source-order"], + "out": "docs", + "theme": "default", + "hideGenerator": "true" + } +} diff --git a/Frontend/ui-library/webpack.common.js b/Frontend/ui-library/webpack.common.js new file mode 100644 index 00000000..b8e03e7a --- /dev/null +++ b/Frontend/ui-library/webpack.common.js @@ -0,0 +1,35 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +const package = require('./package.json'); +const path = require('path'); +const webpack = require('webpack'); + +module.exports = { + entry: { + index: './src/pixelstreamingfrontend-ui.ts' + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: 'ts-loader', + exclude: [/node_modules/] + } + ] + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'] + }, + plugins: [ + new webpack.DefinePlugin({ + LIBRARY_VERSION: JSON.stringify(package.version) + }) + ], + output: { + library: 'lib-pixelstreamingfrontend-ui', // exposed variable that will provide access to the library classes + libraryTarget: 'umd', + path: path.resolve(__dirname, 'dist'), + clean: true, + globalObject: 'this' + } +}; \ No newline at end of file diff --git a/Frontend/ui-library/webpack.dev.js b/Frontend/ui-library/webpack.dev.js new file mode 100644 index 00000000..5ea8f81a --- /dev/null +++ b/Frontend/ui-library/webpack.dev.js @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +const { merge } = require('webpack-merge'); +const common = require('./webpack.common.js'); + +module.exports = merge(common, { + mode: 'development', + devtool: 'inline-source-map', + devServer: { + static: './dist', + }, + output: { + filename: 'lib-pixelstreamingfrontend-ui.js', + } +}); diff --git a/Frontend/ui-library/webpack.prod.js b/Frontend/ui-library/webpack.prod.js new file mode 100644 index 00000000..fa4b6976 --- /dev/null +++ b/Frontend/ui-library/webpack.prod.js @@ -0,0 +1,16 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +const { merge } = require('webpack-merge'); +const common = require('./webpack.common.js'); + +module.exports = merge(common, { + mode: 'production', + optimization: { + usedExports: true, + minimize: true + }, + stats: 'errors-only', + output: { + filename: 'lib-pixelstreamingfrontend-ui.js', + }, +}); diff --git a/RELEASE_VERSION b/RELEASE_VERSION index 6e8bf73a..0ea3a944 100644 --- a/RELEASE_VERSION +++ b/RELEASE_VERSION @@ -1 +1 @@ -0.1.0 +0.2.0 diff --git a/SignallingWebServer/package.json b/SignallingWebServer/package.json index 8f09512b..afa284a7 100644 --- a/SignallingWebServer/package.json +++ b/SignallingWebServer/package.json @@ -4,20 +4,20 @@ "description": "cirrus web server", "scripts": { "store_password": "run-script-os", - "store_password:default": "./platform_scripts/bash/node/bin/node ./modules/authentication/db/store_password.js --usersFile=./modules/authentication/db/users.json", - "store_password:windows": "platform_scripts\\cmd\\node\\node.exe ./modules/authentication/db/store_password.js --usersFile=./modules/authentication/db/users.json", + "store_password:default": "./platform_scripts/bash/node/bin/node ./modules/authentication/db/store_password.js --usersFile=./modules/authentication/db/users.json", + "store_password:windows": "platform_scripts\\cmd\\node\\node.exe ./modules/authentication/db/store_password.js --usersFile=./modules/authentication/db/users.json", "start-local": "run-script-os --", - "start-local:default": "./platform_scripts/bash/Start_Local.sh", - "start-local:windows": ".\\platform_scripts\\cmd\\Start_Local.bat", + "start-local:default": "./platform_scripts/bash/Start_Local.sh", + "start-local:windows": ".\\platform_scripts\\cmd\\Start_Local.bat", "start-signalling-server": "run-script-os --", - "start-signalling-server:default": "./platform_scripts/bash/Start_SignallingServer.sh", - "start-signalling-server:windows": ".\\platform_scripts\\cmd\\Start_SignallingServer.bat", + "start-signalling-server:default": "./platform_scripts/bash/Start_SignallingServer.sh", + "start-signalling-server:windows": ".\\platform_scripts\\cmd\\Start_SignallingServer.bat", "start-with-turn-signalling-server": "run-script-os --", - "start-with-turn-signalling-server:default": "./platform_scripts/bash/Start_WithTurn_SignallingServer.sh", - "start-wiht-turn-signalling-server:windows": ".\\platform_scripts\\cmd\\Start_WithTurn_SignallingServer.bat", + "start-with-turn-signalling-server:default": "./platform_scripts/bash/Start_WithTurn_SignallingServer.sh", + "start-wiht-turn-signalling-server:windows": ".\\platform_scripts\\cmd\\Start_WithTurn_SignallingServer.bat", "start": "run-script-os", - "start:default": "if [ `id -u` -eq 0 ] || [ ! -z $NO_SUDO ]\nthen\n export process=\"./platform_scripts/bash/node/bin/node cirrus.js\"\nelse\n export process=\"sudo ./platform_scripts/bash/node/bin/node cirrus.js\"\nfi\n$process ", - "start:windows": "platform_scripts\\cmd\\node\\node.exe cirrus.js" + "start:default": "if [ `id -u` -eq 0 ] || [ ! -z $NO_SUDO ]\nthen\n export process=\"./platform_scripts/bash/node/bin/node cirrus.js\"\nelse\n export process=\"sudo ./platform_scripts/bash/node/bin/node cirrus.js\"\nfi\n$process ", + "start:windows": "platform_scripts\\cmd\\node\\node.exe cirrus.js" }, "dependencies": { "bcryptjs": "^2.4.3", diff --git a/SignallingWebServer/platform_scripts/bash/setup.sh b/SignallingWebServer/platform_scripts/bash/setup.sh index 1b46c5c0..58cb1b85 100644 --- a/SignallingWebServer/platform_scripts/bash/setup.sh +++ b/SignallingWebServer/platform_scripts/bash/setup.sh @@ -100,10 +100,15 @@ function setup_frontend() { ../../SignallingWebServer/platform_scripts/bash/node/bin/npm install ../../SignallingWebServer/platform_scripts/bash/node/bin/npm run build-dev popd + pushd ${BASH_LOCATION}/../../../Frontend/ui-library > /dev/null + ../../SignallingWebServer/platform_scripts/bash/node/bin/npm install + ../../SignallingWebServer/platform_scripts/bash/node/bin/npm link ../library + ../../SignallingWebServer/platform_scripts/bash/node/bin/npm run build-dev + popd pushd ${BASH_LOCATION}/../../../Frontend/implementations/EpicGames > /dev/null ../../../SignallingWebServer/platform_scripts/bash/node/bin/npm install - ../../../SignallingWebServer/platform_scripts/bash/node/bin/npm link ../../library + ../../../SignallingWebServer/platform_scripts/bash/node/bin/npm link ../../library ../../ui-library ../../../SignallingWebServer/platform_scripts/bash/node/bin/npm run build-dev popd else diff --git a/SignallingWebServer/platform_scripts/cmd/setup_frontend.bat b/SignallingWebServer/platform_scripts/cmd/setup_frontend.bat index 609a5038..b2971e4f 100644 --- a/SignallingWebServer/platform_scripts/cmd/setup_frontend.bat +++ b/SignallingWebServer/platform_scripts/cmd/setup_frontend.bat @@ -55,6 +55,11 @@ call ..\..\SignallingWebServer\platform_scripts\cmd\node\npm install call ..\..\SignallingWebServer\platform_scripts\cmd\node\npm run build-dev popd + pushd %CD%\Frontend\ui-library + call ..\..\SignallingWebServer\platform_scripts\cmd\node\npm install + call ..\..\SignallingWebServer\platform_scripts\cmd\node\npm link ../library + call ..\..\SignallingWebServer\platform_scripts\cmd\node\npm run build-dev + popd echo End of build PS frontend lib step. @Rem Do npm install in the Frontend\implementations\EpicGames directory (note we use start because that loads PATH) @@ -62,7 +67,7 @@ echo Building Epic Games reference frontend... pushd %CD%\Frontend\implementations\EpicGames call ..\..\..\SignallingWebServer\platform_scripts\cmd\node\npm install - call ..\..\..\SignallingWebServer\platform_scripts\cmd\node\npm link ../../library + call ..\..\..\SignallingWebServer\platform_scripts\cmd\node\npm link ../../library ../../ui-library call ..\..\..\SignallingWebServer\platform_scripts\cmd\node\npm run build-dev popd echo End of build reference frontend step.