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
+
+
+
+
+
+
+
+
+
\ 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.