diff --git a/.github/workflows/labgraph_monitor.yml b/.github/workflows/labgraph_monitor.yml new file mode 100644 index 000000000..0f8e40d2b --- /dev/null +++ b/.github/workflows/labgraph_monitor.yml @@ -0,0 +1,22 @@ +on: [push] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [14.x] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: | + cd extensions/prototypes/labgraph_monitor + yarn + - name: Run labgraph_monitor tests + run: | + cd extensions/prototypes/labgraph_monitor + yarn test --watchAll=false + yarn build diff --git a/Dockerfile b/Dockerfile index 0048e1d5a..b6e948f97 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,6 @@ FROM quay.io/pypa/manylinux2014_x86_64 # Install devtoolset-9 -RUN yum update -y RUN yum install -y centos-release-scl RUN yum install -y devtoolset-9 RUN echo "source /opt/rh/devtoolset-9/enable" >> /etc/bashrc diff --git a/extensions/graphviz_support/README.md b/extensions/graphviz_support/README.md index 9bacfeae4..c79d81dd6 100644 --- a/extensions/graphviz_support/README.md +++ b/extensions/graphviz_support/README.md @@ -1,6 +1,6 @@ # Graphviz for LabGraph graphs -This extension provides an API to generate a graphviz visualization of the LabGraph topology. +This extension provides an API to generate a [graphviz](https://graphviz.org/) visualization of the LabGraph topology. ## Quick Start @@ -23,22 +23,33 @@ python setup.py install To make sure things are working: -1- Move to the root of the LabGraph directory: -``` -labgraph\extensions\graphviz_support> cd ../.. +1. Move to the root of the LabGraph directory: + +```bash +labgraph/extensions/graphviz_support> cd ../.. labgraph> ``` -2- Run the following test -``` +2. Run the following test + +```bash python -m extensions.graphviz_support.graphviz_support.tests.test_lg_graphviz_api ``` -**The output of the file for this test can be found at:**\ -extensions\graphviz_support\graphviz_support\tests\output + +**The output of the file for this test can be found at:** + +`extensions/graphviz_support/graphviz_support/tests/output/test.svg` + ### Generating a graphviz file To generate a graph visualization just call 'generate_graphviz' function and pass the appropriate parameters -``` -from extensions/graphviz_support/graphviz_support/generate_graphviz/generate_graphviz.py import generate_graphviz.py -generate_graphviz(graph, output_file) +```python +from extensions.graphviz_support.graphviz_support.generate_graphviz.generate_graphviz import generate_graphviz +from extensions.graphviz_support.graphviz_support.tests.demo_graph.demo import Demo + +generate_graphviz(Demo(), "output.png") # you can also use "output.svg" ``` + +**It will produce the following diagram named `output.png` in your current directory:** + +![Graphviz diagram for nodes: RollingAverager, NoiseGenerator, Amplifier, Sink, and Attenuator](https://i.imgur.com/M4yL39x.png) diff --git a/extensions/graphviz_support/graphviz_support/tests/demo_graph/amplifier.py b/extensions/graphviz_support/graphviz_support/tests/demo_graph/amplifier.py index 1ac52cd66..9d5a1067e 100644 --- a/extensions/graphviz_support/graphviz_support/tests/demo_graph/amplifier.py +++ b/extensions/graphviz_support/graphviz_support/tests/demo_graph/amplifier.py @@ -12,20 +12,20 @@ class AmplifierConfig(lg.Config): class Amplifier(lg.Node): - INPUT = lg.Topic(RandomMessage) - OUTPUT = lg.Topic(RandomMessage) + AMPLIFIER_INPUT = lg.Topic(RandomMessage) + AMPLIFIER_OUTPUT = lg.Topic(RandomMessage) config: AmplifierConfig def output(self, _in: float) -> float: return self.config.out_in_ratio * _in - @lg.subscriber(INPUT) - @lg.publisher(OUTPUT) + @lg.subscriber(AMPLIFIER_INPUT) + @lg.publisher(AMPLIFIER_OUTPUT) async def amplify(self, message: RandomMessage) -> lg.AsyncPublisher: current_time = time.time() output_data = np.array( [self.output(_in) for _in in message.data] ) - yield self.OUTPUT, RandomMessage( + yield self.AMPLIFIER_OUTPUT, RandomMessage( timestamp=current_time, data=output_data ) diff --git a/extensions/graphviz_support/graphviz_support/tests/demo_graph/attenuator.py b/extensions/graphviz_support/graphviz_support/tests/demo_graph/attenuator.py index 55ecdb022..d90a5c6d6 100644 --- a/extensions/graphviz_support/graphviz_support/tests/demo_graph/attenuator.py +++ b/extensions/graphviz_support/graphviz_support/tests/demo_graph/attenuator.py @@ -12,20 +12,20 @@ class AttenuatorConfig(lg.Config): class Attenuator(lg.Node): - INPUT = lg.Topic(RandomMessage) - OUTPUT = lg.Topic(RandomMessage) + ATTENUATOR_INPUT = lg.Topic(RandomMessage) + ATTENUATOR_OUTPUT = lg.Topic(RandomMessage) config: AttenuatorConfig def output(self, _in: float) -> float: return pow(10, (self.config.attenuation / 20)) * _in - @lg.subscriber(INPUT) - @lg.publisher(OUTPUT) + @lg.subscriber(ATTENUATOR_INPUT) + @lg.publisher(ATTENUATOR_OUTPUT) async def attenuate(self, message: RandomMessage) -> lg.AsyncPublisher: current_time = time.time() output_data = np.array( [self.output(_in) for _in in message.data] ) - yield self.OUTPUT, RandomMessage( + yield self.ATTENUATOR_OUTPUT, RandomMessage( timestamp=current_time, data=output_data ) diff --git a/extensions/graphviz_support/graphviz_support/tests/demo_graph/demo.py b/extensions/graphviz_support/graphviz_support/tests/demo_graph/demo.py index 27237c942..3dadf96c4 100644 --- a/extensions/graphviz_support/graphviz_support/tests/demo_graph/demo.py +++ b/extensions/graphviz_support/graphviz_support/tests/demo_graph/demo.py @@ -47,12 +47,12 @@ def setup(self) -> None: def connections(self) -> lg.Connections: return ( - (self.NOISE_GENERATOR.OUTPUT, self.ROLLING_AVERAGER.INPUT), - (self.NOISE_GENERATOR.OUTPUT, self.AMPLIFIER.INPUT), - (self.NOISE_GENERATOR.OUTPUT, self.ATTENUATOR.INPUT), - (self.ROLLING_AVERAGER.OUTPUT, self.SINK.INPUT_1), - (self.AMPLIFIER.OUTPUT, self.SINK.INPUT_2), - (self.ATTENUATOR.OUTPUT, self.SINK.INPUT_3), + (self.NOISE_GENERATOR.NOISE_GENERATOR_OUTPUT, self.ROLLING_AVERAGER.ROLLING_AVERAGER_INPUT), + (self.NOISE_GENERATOR.NOISE_GENERATOR_OUTPUT, self.AMPLIFIER.AMPLIFIER_INPUT), + (self.NOISE_GENERATOR.NOISE_GENERATOR_OUTPUT, self.ATTENUATOR.ATTENUATOR_INPUT), + (self.ROLLING_AVERAGER.ROLLING_AVERAGER_OUTPUT, self.SINK.SINK_INPUT_1), + (self.AMPLIFIER.AMPLIFIER_OUTPUT, self.SINK.SINK_INPUT_2), + (self.ATTENUATOR.ATTENUATOR_OUTPUT, self.SINK.SINK_INPUT_3), ) def process_modules(self) -> Tuple[lg.Module, ...]: diff --git a/extensions/graphviz_support/graphviz_support/tests/demo_graph/noise_generator.py b/extensions/graphviz_support/graphviz_support/tests/demo_graph/noise_generator.py index f42aef9f1..31f04e052 100644 --- a/extensions/graphviz_support/graphviz_support/tests/demo_graph/noise_generator.py +++ b/extensions/graphviz_support/graphviz_support/tests/demo_graph/noise_generator.py @@ -14,13 +14,13 @@ class NoiseGeneratorConfig(lg.Config): class NoiseGenerator(lg.Node): - OUTPUT = lg.Topic(RandomMessage) + NOISE_GENERATOR_OUTPUT = lg.Topic(RandomMessage) config: NoiseGeneratorConfig - @lg.publisher(OUTPUT) + @lg.publisher(NOISE_GENERATOR_OUTPUT) async def generate_noise(self) -> lg.AsyncPublisher: while True: - yield self.OUTPUT, RandomMessage( + yield self.NOISE_GENERATOR_OUTPUT, RandomMessage( timestamp=time.time(), data=np.random.rand(self.config.num_features) ) diff --git a/extensions/graphviz_support/graphviz_support/tests/demo_graph/rolling_averager.py b/extensions/graphviz_support/graphviz_support/tests/demo_graph/rolling_averager.py index 90bc78575..c4d51a463 100644 --- a/extensions/graphviz_support/graphviz_support/tests/demo_graph/rolling_averager.py +++ b/extensions/graphviz_support/graphviz_support/tests/demo_graph/rolling_averager.py @@ -18,14 +18,14 @@ class RollingConfig(lg.Config): class RollingAverager(lg.Node): - INPUT = lg.Topic(RandomMessage) - OUTPUT = lg.Topic(RandomMessage) + ROLLING_AVERAGER_INPUT = lg.Topic(RandomMessage) + ROLLING_AVERAGER_OUTPUT = lg.Topic(RandomMessage) state: RollingState config: RollingConfig - @lg.subscriber(INPUT) - @lg.publisher(OUTPUT) + @lg.subscriber(ROLLING_AVERAGER_INPUT) + @lg.publisher(ROLLING_AVERAGER_OUTPUT) async def average(self, message: RandomMessage) -> lg.AsyncPublisher: current_time = time.time() self.state.messages.append(message) @@ -40,7 +40,7 @@ async def average(self, message: RandomMessage) -> lg.AsyncPublisher: [message.data for message in self.state.messages] ) mean_data = np.mean(all_data, axis=0) - yield self.OUTPUT, RandomMessage( + yield self.ROLLING_AVERAGER_OUTPUT, RandomMessage( timestamp=current_time, data=mean_data ) diff --git a/extensions/graphviz_support/graphviz_support/tests/demo_graph/sink.py b/extensions/graphviz_support/graphviz_support/tests/demo_graph/sink.py index 59c340b6b..3736482ec 100644 --- a/extensions/graphviz_support/graphviz_support/tests/demo_graph/sink.py +++ b/extensions/graphviz_support/graphviz_support/tests/demo_graph/sink.py @@ -14,20 +14,20 @@ class SinkState(lg.State): class Sink(lg.Node): - INPUT_1 = lg.Topic(RandomMessage) - INPUT_2 = lg.Topic(RandomMessage) - INPUT_3 = lg.Topic(RandomMessage) + SINK_INPUT_1 = lg.Topic(RandomMessage) + SINK_INPUT_2 = lg.Topic(RandomMessage) + SINK_INPUT_3 = lg.Topic(RandomMessage) state: SinkState - @lg.subscriber(INPUT_1) + @lg.subscriber(SINK_INPUT_1) def got_message(self, message: RandomMessage) -> None: self.state.data_1 = message.data - @lg.subscriber(INPUT_2) + @lg.subscriber(SINK_INPUT_2) def got_message_2(self, message: RandomMessage) -> None: self.state.data_2 = message.data - @lg.subscriber(INPUT_3) + @lg.subscriber(SINK_INPUT_3) def got_message_3(self, message: RandomMessage) -> None: self.state.data_3 = message.data diff --git a/extensions/graphviz_support/graphviz_support/tests/output/test b/extensions/graphviz_support/graphviz_support/tests/output/test new file mode 100644 index 000000000..b7eb1c3b1 --- /dev/null +++ b/extensions/graphviz_support/graphviz_support/tests/output/test @@ -0,0 +1,17 @@ +digraph Demo { + center=true rankdir=LR + node [fontsize=12 height=1.5 shape=circle width=1.5] + NoiseGenerator + RollingAverager + Amplifier + Attenuator + Sink + Sink + Sink + NoiseGenerator -> RollingAverager + NoiseGenerator -> Amplifier + NoiseGenerator -> Attenuator + RollingAverager -> Sink + Amplifier -> Sink + Attenuator -> Sink +} diff --git a/extensions/graphviz_support/graphviz_support/tests/output/test.svg b/extensions/graphviz_support/graphviz_support/tests/output/test.svg new file mode 100644 index 000000000..daf81fccc --- /dev/null +++ b/extensions/graphviz_support/graphviz_support/tests/output/test.svg @@ -0,0 +1,79 @@ + + + + + + +Demo + + + +NoiseGenerator + +NoiseGenerator + + + +RollingAverager + +RollingAverager + + + +NoiseGenerator->RollingAverager + + + + + +Amplifier + +Amplifier + + + +NoiseGenerator->Amplifier + + + + + +Attenuator + +Attenuator + + + +NoiseGenerator->Attenuator + + + + + +Sink + +Sink + + + +RollingAverager->Sink + + + + + +Amplifier->Sink + + + + + +Attenuator->Sink + + + + + diff --git a/extensions/graphviz_support/graphviz_support/tests/test_lg_graphviz_api.py b/extensions/graphviz_support/graphviz_support/tests/test_lg_graphviz_api.py index ea63d4391..2711b5644 100644 --- a/extensions/graphviz_support/graphviz_support/tests/test_lg_graphviz_api.py +++ b/extensions/graphviz_support/graphviz_support/tests/test_lg_graphviz_api.py @@ -33,47 +33,47 @@ def test_out_edge_node_mapper(self) -> None: self.assertEqual(4, len(out_edge_node_map)) self.assertEqual( 'generate_noise', - out_edge_node_map['NOISE_GENERATOR/OUTPUT'].name + out_edge_node_map['NOISE_GENERATOR/NOISE_GENERATOR_OUTPUT'].name ) self.assertEqual( 'average', - out_edge_node_map['ROLLING_AVERAGER/OUTPUT'].name + out_edge_node_map['ROLLING_AVERAGER/ROLLING_AVERAGER_OUTPUT'].name ) self.assertEqual( 'amplify', - out_edge_node_map['AMPLIFIER/OUTPUT'].name + out_edge_node_map['AMPLIFIER/AMPLIFIER_OUTPUT'].name ) self.assertEqual( 'attenuate', - out_edge_node_map['ATTENUATOR/OUTPUT'].name + out_edge_node_map['ATTENUATOR/ATTENUATOR_OUTPUT'].name ) def test_in_out_edge_mapper(self) -> None: in_out_edge_map = in_out_edge_mapper(self.graph.__streams__.values()) self.assertEqual(6, len(in_out_edge_map)) self.assertEqual( - 'NOISE_GENERATOR/OUTPUT', - in_out_edge_map['ROLLING_AVERAGER/INPUT'] + 'NOISE_GENERATOR/NOISE_GENERATOR_OUTPUT', + in_out_edge_map['ROLLING_AVERAGER/ROLLING_AVERAGER_INPUT'] ) self.assertEqual( - 'NOISE_GENERATOR/OUTPUT', - in_out_edge_map['AMPLIFIER/INPUT'] + 'NOISE_GENERATOR/NOISE_GENERATOR_OUTPUT', + in_out_edge_map['AMPLIFIER/AMPLIFIER_INPUT'] ) self.assertEqual( - 'NOISE_GENERATOR/OUTPUT', - in_out_edge_map['ATTENUATOR/INPUT'] + 'NOISE_GENERATOR/NOISE_GENERATOR_OUTPUT', + in_out_edge_map['ATTENUATOR/ATTENUATOR_INPUT'] ) self.assertEqual( - 'ROLLING_AVERAGER/OUTPUT', - in_out_edge_map['SINK/INPUT_1'] + 'ROLLING_AVERAGER/ROLLING_AVERAGER_OUTPUT', + in_out_edge_map['SINK/SINK_INPUT_1'] ) self.assertEqual( - 'AMPLIFIER/OUTPUT', - in_out_edge_map['SINK/INPUT_2'] + 'AMPLIFIER/AMPLIFIER_OUTPUT', + in_out_edge_map['SINK/SINK_INPUT_2'] ) self.assertEqual( - 'ATTENUATOR/OUTPUT', - in_out_edge_map['SINK/INPUT_3'] + 'ATTENUATOR/ATTENUATOR_OUTPUT', + in_out_edge_map['SINK/SINK_INPUT_3'] ) def test_connect_to_upstream(self) -> None: diff --git a/extensions/prototypes/labgraph_monitor/.eslintignore b/extensions/prototypes/labgraph_monitor/.eslintignore new file mode 100644 index 000000000..c523dda43 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/.eslintignore @@ -0,0 +1,2 @@ +node_modules +public \ No newline at end of file diff --git a/extensions/prototypes/labgraph_monitor/.eslintrc.json b/extensions/prototypes/labgraph_monitor/.eslintrc.json new file mode 100644 index 000000000..fde66f482 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["react-app", "react-app/jest", "prettier"], + "plugins": ["prettier"], + "rules": { + "prettier/prettier": [ + "error", + { + "endOfLine": "auto" + } + ], + "no-console": [ + "warn", + { + "allow": ["warn", "error"] + } + ] + } +} diff --git a/extensions/prototypes/labgraph_monitor/.gitignore b/extensions/prototypes/labgraph_monitor/.gitignore index a00fddb79..d3ff5fc90 100644 --- a/extensions/prototypes/labgraph_monitor/.gitignore +++ b/extensions/prototypes/labgraph_monitor/.gitignore @@ -4,7 +4,6 @@ /node_modules /.pnp .pnp.js -/public # testing /coverage @@ -22,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +package-lock.json \ No newline at end of file diff --git a/extensions/prototypes/labgraph_monitor/.husky/pre-commit b/extensions/prototypes/labgraph_monitor/.husky/pre-commit new file mode 100644 index 000000000..546369c29 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/.husky/pre-commit @@ -0,0 +1,8 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" +cd ./extensions/prototypes/labgraph_monitor +SRC_PATTERN="extensions/prototypes/labgraph_monitor" +git diff --cached --name-only | if grep --quiet "$SRC_PATTERN" +then + yarn check-code +fi \ No newline at end of file diff --git a/extensions/prototypes/labgraph_monitor/.prettierignore b/extensions/prototypes/labgraph_monitor/.prettierignore new file mode 100644 index 000000000..fc1666b42 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/.prettierignore @@ -0,0 +1,2 @@ +public +node_modules \ No newline at end of file diff --git a/extensions/prototypes/labgraph_monitor/.prettierrc.json b/extensions/prototypes/labgraph_monitor/.prettierrc.json new file mode 100644 index 000000000..00a58d4d7 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "trailingComma": "es5", + "tabWidth": 4, + "semi": true, + "singleQuote": true, + "endOfLine": "auto" +} diff --git a/extensions/prototypes/labgraph_monitor/App.css b/extensions/prototypes/labgraph_monitor/App.css deleted file mode 100644 index 40d5810d5..000000000 --- a/extensions/prototypes/labgraph_monitor/App.css +++ /dev/null @@ -1,21 +0,0 @@ -table{ - border: 1px solid black; - text-align: center; - margin-right:10px; - margin-top: 20px; -} - -th, td{ - border: 1px solid black; - text-align: center; -} - -.annotations{ - align-items: center; - margin-right:50px; - margin-top: 15px; -} - -.table-div{ - float:right; -} \ No newline at end of file diff --git a/extensions/prototypes/labgraph_monitor/App.js b/extensions/prototypes/labgraph_monitor/App.js deleted file mode 100644 index 5497b02af..000000000 --- a/extensions/prototypes/labgraph_monitor/App.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - -Copyright (c) Facebook, Inc. and its affiliates. -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -import React, { useState, useEffect, useCallback} from 'react'; -import { w3cwebsocket as W3CWebSocket } from "websocket"; -import ReactFlow, { addEdge, MiniMap, Controls, Background, isNode} from 'react-flow-renderer'; -import { dataToConnections, connectionsToNodes, dataToObjects } from './helper'; -import './App.css'; -import dagre from 'dagre'; - -// sample connections array: {'NoiseGenerator': ['RollingAverager'], 'RollingAverager': ['AveragedNoise'], 'AveragedNoise': ['Plot']} - -// dagreGraph helps layout the nodes in the graph -const dagreGraph = new dagre.graphlib.Graph(); -dagreGraph.setDefaultEdgeLabel(() => ({})); - -const nodeWidth = 172; -const nodeHeight = 36; - -// setting up the horizontal and vertical options to change the orientation -const getLayoutedElements = (elements, direction = 'TB') => { - const isHorizontal = direction === 'LR'; - dagreGraph.setGraph({ rankdir: direction }); - - elements.forEach((el) => { - if (isNode(el)) { - dagreGraph.setNode(el.id, { width: nodeWidth, height: nodeHeight }); - } else { - dagreGraph.setEdge(el.source, el.target); - } - }); - - dagre.layout(dagreGraph); - - return elements.map((el) => { - if (isNode(el)) { - const nodeWithPosition = dagreGraph.node(el.id); - el.targetPosition = isHorizontal ? 'left' : 'top'; - el.sourcePosition = isHorizontal ? 'right' : 'bottom'; - el.position = { - x: nodeWithPosition.x - nodeWidth / 2 + Math.random() / 1000, - y: nodeWithPosition.y - nodeHeight / 2, - }; - } - - return el; - }); -}; - -const InteractionGraph = () => { - const [node_name, setName] = useState("None"); - const [connections, setConnections] = useState([]); - const [elements, setElements] = useState([]); - const [nodeObjects, setNodeObjects] = useState([]); - const [type, setType] = useState('None') - - // getting the node that the user is clicking - const onElementClick = (event, element) => { - setName(element.id) - setType(nodeObjects[element.id].type) - console.log("node", node_name) - }; - - // receiving the messages from server - useEffect(() => { - // connecting to the server - const client = new W3CWebSocket('ws://localhost:9000'); - - client.onopen = () => { - console.log('connected'); - client.send(JSON.stringify({ - "api_version": "0.1", - "api_request": { - "request_id": 1, - "start_stream_request": { - "stream_id": "LABGRAPH.MONITOR", - "labgraph.monitor": { - } - } - } - })) - }; - - client.onmessage = (message) => { - const dataFromServer = JSON.parse(message.data); - if (dataFromServer.stream_batch){ - const dataArray = dataFromServer.stream_batch["labgraph.monitor"].samples[0].data; // dataArray is the part of JSON data we are interested in - const connections = dataToConnections(dataArray); // generating the connections array - const elements = connectionsToNodes(connections); // changing the connections array to an elements array having the properties of nodes in reactflow - const layoutedElements = getLayoutedElements(elements); // using dagreGraph and the getLayoutedElements function defined earlier to layout the nodes - const nodeObjects = dataToObjects(dataArray); - setElements(layoutedElements); - setConnections(connections); - setNodeObjects(nodeObjects); - console.log("nodeObjects", nodeObjects); - console.log("connection", connections); - console.log("server message received ", dataArray) - } - }; - - return () => { - client.close(); - } - }, []); - - const onLayout = useCallback( - (direction) => { - const layoutedElements = getLayoutedElements(elements, direction); - setElements(layoutedElements); - }, - [elements] - ); - - return( -
-
-
-
- - -
- - - - - - - - - - - - - -
NodeType
{node_name}{type}
-
- -
- - - - - -
- -
- - - ) -}; - -export default InteractionGraph; \ No newline at end of file diff --git a/extensions/prototypes/labgraph_monitor/README.md b/extensions/prototypes/labgraph_monitor/README.md index 27a20d41c..a5b1566b3 100644 --- a/extensions/prototypes/labgraph_monitor/README.md +++ b/extensions/prototypes/labgraph_monitor/README.md @@ -1,77 +1,95 @@ -# LabGraph Monitor +# Labgraph Monitor -LabGraph Monitor helps visualize the graph built. In order to run the application, you will need to first set it up using React using the below steps: +This extension is an interactive visualization tool to monitor and make real-time changes to LabGraph graph and nodes. -In case you do not have npm installed, follow the below steps to get started: +## Quick Start -Download Node.js from [here](https://nodejs.org/en/download/) +**Prerequisites**: -Once Node is installed, you can verify that node/npm was installed successfully by running the following: +- [Node.js](https://nodejs.org/en/) +- [Yarn](https://classic.yarnpkg.com/lang/en/docs/install) + +Check that node and yarn were properly installed by running the following commands ``` node -v -npm -v ``` -The above should display the version of node/npm that were installed. For example: - ``` -6.14.13 +yarn -v ``` -Now that you have npm installed, you can create a new React application using the terminal. Navigate to the folder where you want the application to be installed and run the below code to create a new React application. We are going to call the application "labgraph_monitor_extension" for the sake of this example but feel free to choose another name. +**Set up the application** + +1. Be sure that you are inside **extensions/prototypes/labgraph_monitor** directory -Note: npx is a package that comes with npm 5.2+ ``` -npx create-react-app labgraph_monitor_extension +cd extensions/prototypes/labgraph_monitor ``` -Next, you will need to install the dagre library, websocket, and ReactFlow that help in layouting the graph. -Run the code below to install them +2. Install dependencies by running **yarn** command + ``` -npm i dagre +yarn ``` +3. Test the application by running the following command + ``` -npm install websocket +yarn test --watchAll=false ``` +4. Run the application + ``` -npm install --save react-flow-renderer +yarn start ``` -Once the application is created, we will need to change and add some files to get the LabGraph Monitor working. +(!) The application will be running on **localhost:3000** by default -First, you can validate that everything is going well so far by running the new sample React app in your browser. Navigate using the terminal to the labgraph_monitor_extension folder and run +## UI Overview -``` -npm start -``` + + +### Mocks: -This should open a sample React app in your localhost and display the React icon. +Mocks are a quick way to get familiar with the user interface. They do not require any connection to the LabGraph Websockets API and provide visualization for most of the existing [LabGraph examples](https://github.com/facebookresearch/labgraph/tree/main/labgraph/examples). -Now that we have the initial application set up, we can start adding the new files. +Mocks also can be useful to experiment with new UI features. -Add the App.js, helper.js and App.css files downloaded from [labgraph repo](https://github.com/facebookresearch/labgraph) found at .\labgraph\extensions\prototypes\labgraph_monitor\src to your labgraph_monitor_extension application. You need to add them in the ./labgraph_monitor_extension/src folder +### Realtime: -You can now navigate to your localhost in browser again to see the changes. As the backend server is not running yet, you will see a blank white page. +This is the core feature of Labgraph Monitor, it provides real-time visualization of the graph by connecting to Labgraph Websockets API. -Note: incase you closed your react app while downloading other libraries, you will need to start it again by navigating to the labgraph_monitor_extension and running: +To use this feature properly few steps are required: + +1. Within **extensions/prototypes/labgraph_monitor** directory, Create a **.env.local** that contains the following information ``` -npm start +REACT_APP_WS_API="ws://127.0.0.1:9000" ``` -With that, LabGraph Monitor is ready! Before you see the graph displayed in your browser, you will need to start the backend server. +(!) LabGraph Websocket server runs on localhost:9000 by default -## Running an example +2. Run Labgraph Websockets server. The following tutorial shows how to run LabGraph Websocket server properly : [tutorial](https://github.com/facebookresearch/labgraph/pull/58/files#diff-247005c77570899ce53f81a83b2a5fe6e7535616cc96564d67378fe7f73dac49) -Inorder to run a sample graph, you can go to the simpleVizGraph.js file found under labgraph_monitor/examples/simpleVizGraph.js and replace the current code in App.js with the simpleVizGraph.js code. Next, you can start the application using +3. Under LabGraph Monitor settings panel click on REALTIME option and click connect. -``` -npm start -``` +### Nodes & Edges + +To see the information related to a specific node or edge, just click on it, the appropriate information will be automatically displayed on the setting panel. + + + + + + + + + + +
NodeEdge
-This code shows the sample graph of the simple_viz.py example found under labgraph/examples of the main repository. It works by having the expected JSON data the server should send using the parser and rendering it through our React application. +**Nodes** : currently when a node is clicked its name will be displayed, However, this feature will be updated in the future to include more information. -You can also try other another example by following the above steps on the mockDataGraph.js found under labgraph_monitor/examples/mockDataGraph.js \ No newline at end of file +**Edges** : currently when an edge is clicked the "message_name", "message_fields" and "fields_datatypes" will be displayed, However, this feature will be updated in the future to include more information (E.g: the field value in realtime). diff --git a/extensions/prototypes/labgraph_monitor/examples/mockDataGraph.js b/extensions/prototypes/labgraph_monitor/examples/mockDataGraph.js deleted file mode 100644 index 0b7244ae4..000000000 --- a/extensions/prototypes/labgraph_monitor/examples/mockDataGraph.js +++ /dev/null @@ -1,477 +0,0 @@ -/** - -Copyright (c) Facebook, Inc. and its affiliates. -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -import React, { useState, useEffect, useCallback} from 'react'; -import ReactFlow, { addEdge, MiniMap, Controls, Background, isNode} from 'react-flow-renderer'; -import { dataToConnections, connectionsToNodes, dataToObjects } from './helper'; -import './App.css'; -import dagre from 'dagre'; - -const data = [ - [ - { - "NoiseGenerator": { - "type": "Node", - "config": { - "NoiseGeneratorConfig": { - "sample_rate": "float", - "num_features": "int" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ] - } - }, - { - "RollingAverager": { - "type": "Node", - "state": { - "RollingState": { - "messages": "List.RandomMessage" - } - }, - "config": { - "RollingConfig": { - "window": "float" - } - }, - "inputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ] - } - } - ], - [ - { - "RollingAverager": { - "type": "Node", - "state": { - "RollingState": { - "messages": "List.RandomMessage" - } - }, - "config": { - "RollingConfig": { - "window": "float" - } - }, - "inputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ] - } - }, - { - "AveragedNoise": { - "type": "Group", - "config": { - "AveragedNoiseConfig": { - "sample_rate": "float", - "num_features": "int", - "window": "float" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "connections": { - "NoiseGenerator": "RollingAverager", - "RollingAverager": "AveragedNoise", - "RollingAverager": "Node2", - "RollingAverager": "Node4", - "RollingAverager": "Node5", - "Node2": "Node6", - "Node2": "Node7", - } - } - }, - { - "Node2": { - "type": "Group", - "config": { - "Node2Config": { - "sample_rate": "float", - "num_features": "int", - "window": "float" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "connections": { - "NoiseGenerator": "RollingAverager", - "RollingAverager": "AveragedNoise", - "RollingAverager": "Node2", - "RollingAverager": "Node4", - "RollingAverager": "Node5", - "Node2": "Node6", - "Node2": "Node7", - } - } - }, - { - "Node4": { - "type": "Group", - "config": { - "Node2Config": { - "sample_rate": "float", - "num_features": "int", - "window": "float" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "connections": { - "NoiseGenerator": "RollingAverager", - "RollingAverager": "AveragedNoise", - "RollingAverager": "Node2", - "RollingAverager": "Node4", - "RollingAverager": "Node5", - "Node2": "Node6", - "Node2": "Node7", - } - } - }, - { - "Node5": { - "type": "Group", - "config": { - "Node2Config": { - "sample_rate": "float", - "num_features": "int", - "window": "float" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "connections": { - "NoiseGenerator": "RollingAverager", - "RollingAverager": "AveragedNoise", - "RollingAverager": "Node2", - "RollingAverager": "Node4", - "RollingAverager": "Node5", - "Node2": "Node6", - "Node2": "Node7", - } - } - }, - ], - [ - { - "AveragedNoise": { - "type": "Group", - "config": { - "AveragedNoiseConfig": { - "sample_rate": "float", - "num_features": "int", - "window": "float" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "connections": { - "NoiseGenerator": "RollingAverager", - "RollingAverager": "AveragedNoise", - "RollingAverager": "Node2", - "RollingAverager": "Node4", - "RollingAverager": "Node5", - "Node2": "Node6", - "Node2": "Node7", - } - } - }, - { - "Plot": { - "type": "Node", - "state": "PlotState", - "config": "PlotConfig", - "inputs": [ - "RandomMessage" - ], - "outputs": [] - } - } - ], - [ - { - "Node2": { - "type": "Group", - "config": { - "AveragedNoiseConfig": { - "sample_rate": "float", - "num_features": "int", - "window": "float" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "connections": { - "NoiseGenerator": "RollingAverager", - "RollingAverager": "AveragedNoise", - "RollingAverager": "Node2", - "RollingAverager": "Node4", - "RollingAverager": "Node5", - "Node2": "Node6", - "Node2": "Node7", - } - } - }, - { - "Node6": { - "type": "Group", - "config": { - "Node2Config": { - "sample_rate": "float", - "num_features": "int", - "window": "float" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "connections": { - "NoiseGenerator": "RollingAverager", - "RollingAverager": "AveragedNoise", - "RollingAverager": "Node2", - "RollingAverager": "Node4", - "RollingAverager": "Node5", - "Node2": "Node6", - "Node2": "Node7", - } - } - }, - { - "Node7": { - "type": "Group", - "config": { - "Node2Config": { - "sample_rate": "float", - "num_features": "int", - "window": "float" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "connections": { - "NoiseGenerator": "RollingAverager", - "RollingAverager": "AveragedNoise", - "RollingAverager": "Node2", - "RollingAverager": "Node4", - "RollingAverager": "Node5", - "Node2": "Node6", - "Node2": "Node7", - } - } - }, - ] -] - -// dagreGraph helps layout the nodes in the graph -const dagreGraph = new dagre.graphlib.Graph(); -dagreGraph.setDefaultEdgeLabel(() => ({})); - -const nodeWidth = 172; -const nodeHeight = 36; - -// setting up the horizontal and vertical options to change the orientation -const getLayoutedElements = (elements, direction = 'TB') => { - const isHorizontal = direction === 'LR'; - dagreGraph.setGraph({ rankdir: direction }); - - elements.forEach((el) => { - if (isNode(el)) { - dagreGraph.setNode(el.id, { width: nodeWidth, height: nodeHeight }); - } else { - dagreGraph.setEdge(el.source, el.target); - } - }); - - dagre.layout(dagreGraph); - - return elements.map((el) => { - if (isNode(el)) { - const nodeWithPosition = dagreGraph.node(el.id); - el.targetPosition = isHorizontal ? 'left' : 'top'; - el.sourcePosition = isHorizontal ? 'right' : 'bottom'; - el.position = { - x: nodeWithPosition.x - nodeWidth / 2 + Math.random() / 1000, - y: nodeWithPosition.y - nodeHeight / 2, - }; - } - - return el; - }); -}; - -const InteractionGraph = () => { - const [node_name, setName] = useState("None"); - const [connections, setConnections] = useState([]); - const [elements, setElements] = useState([]); - const [nodeObjects, setNodeObjects] = useState([]); - const [type, setType] = useState('None') - - // getting the node that the user is clicking - const onElementClick = (event, element) => { - setName(element.id) - setType(nodeObjects[element.id].type) - console.log("node", node_name) - }; - - // receiving the messages from server - useEffect(() => { - const connections = dataToConnections(data); // generating the connections array - const elements = connectionsToNodes(connections); // changing the connections array to an elements array having the properties of nodes in reactflow - const layoutedElements = getLayoutedElements(elements); // using dagreGraph and the getLayoutedElements function defined earlier to layout the nodes - const nodeObjects = dataToObjects(data); - setElements(layoutedElements); - setConnections(connections); - setNodeObjects(nodeObjects); - console.log("nodeObjects", nodeObjects); - console.log("connection", connections); - console.log("server message received ", data) - - return () => { - } - }, []); - - const onLayout = useCallback( - (direction) => { - const layoutedElements = getLayoutedElements(elements, direction); - setElements(layoutedElements); - }, - [elements] - ); - - return( -
-
-
-
- - -
- - - - - - - - - - - - - -
NodeType
{node_name}{type}
-
- -
- - - - - -
- -
- - - ) -}; - -export default InteractionGraph; \ No newline at end of file diff --git a/extensions/prototypes/labgraph_monitor/examples/simpleVizGraph.js b/extensions/prototypes/labgraph_monitor/examples/simpleVizGraph.js deleted file mode 100644 index f40bb5165..000000000 --- a/extensions/prototypes/labgraph_monitor/examples/simpleVizGraph.js +++ /dev/null @@ -1,285 +0,0 @@ -/** - -Copyright (c) Facebook, Inc. and its affiliates. -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -import React, { useState, useEffect, useCallback} from 'react'; -import ReactFlow, { addEdge, MiniMap, Controls, Background, isNode} from 'react-flow-renderer'; -import { dataToConnections, connectionsToNodes, dataToObjects } from './helper'; -import './App.css'; -import dagre from 'dagre'; - -const data = [ - [ - { - "NoiseGenerator": { - "type": "Node", - "config": { - "NoiseGeneratorConfig": { - "sample_rate": "float", - "num_features": "int" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ] - } - }, - { - "RollingAverager": { - "type": "Node", - "state": { - "RollingState": { - "messages": "List.RandomMessage" - } - }, - "config": { - "RollingConfig": { - "window": "float" - } - }, - "inputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ] - } - } - ], - [ - { - "RollingAverager": { - "type": "Node", - "state": { - "RollingState": { - "messages": "List.RandomMessage" - } - }, - "config": { - "RollingConfig": { - "window": "float" - } - }, - "inputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ] - } - }, - { - "AveragedNoise": { - "type": "Group", - "config": { - "AveragedNoiseConfig": { - "sample_rate": "float", - "num_features": "int", - "window": "float" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "connections": { - "NoiseGenerator": "RollingAverager", - "RollingAverager": "AveragedNoise" - } - } - } - ], - [ - { - "AveragedNoise": { - "type": "Group", - "config": { - "AveragedNoiseConfig": { - "sample_rate": "float", - "num_features": "int", - "window": "float" - } - }, - "inputs": [], - "outputs": [ - { - "RandomMessage": { - "timestamp": "float", - "data": "np.ndarray" - } - } - ], - "connections": { - "NoiseGenerator": "RollingAverager", - "RollingAverager": "AveragedNoise" - } - } - }, - { - "Plot": { - "type": "Node", - "state": "PlotState", - "config": "PlotConfig", - "inputs": [ - "RandomMessage" - ], - "outputs": [] - } - } - ] -] - -// dagreGraph helps layout the nodes in the graph -const dagreGraph = new dagre.graphlib.Graph(); -dagreGraph.setDefaultEdgeLabel(() => ({})); - -const nodeWidth = 172; -const nodeHeight = 36; - -// setting up the horizontal and vertical options to change the orientation -const getLayoutedElements = (elements, direction = 'TB') => { - const isHorizontal = direction === 'LR'; - dagreGraph.setGraph({ rankdir: direction }); - - elements.forEach((el) => { - if (isNode(el)) { - dagreGraph.setNode(el.id, { width: nodeWidth, height: nodeHeight }); - } else { - dagreGraph.setEdge(el.source, el.target); - } - }); - - dagre.layout(dagreGraph); - - return elements.map((el) => { - if (isNode(el)) { - const nodeWithPosition = dagreGraph.node(el.id); - el.targetPosition = isHorizontal ? 'left' : 'top'; - el.sourcePosition = isHorizontal ? 'right' : 'bottom'; - el.position = { - x: nodeWithPosition.x - nodeWidth / 2 + Math.random() / 1000, - y: nodeWithPosition.y - nodeHeight / 2, - }; - } - - return el; - }); -}; - -const InteractionGraph = () => { - const [node_name, setName] = useState("None"); - const [connections, setConnections] = useState([]); - const [elements, setElements] = useState([]); - const [nodeObjects, setNodeObjects] = useState([]); - const [type, setType] = useState('None') - - // getting the node that the user is clicking - const onElementClick = (event, element) => { - setName(element.id) - setType(nodeObjects[element.id].type) - console.log("node", node_name) - }; - - // receiving the messages from server - useEffect(() => { - const connections = dataToConnections(data); // generating the connections array - const elements = connectionsToNodes(connections); // changing the connections array to an elements array having the properties of nodes in reactflow - const layoutedElements = getLayoutedElements(elements); // using dagreGraph and the getLayoutedElements function defined earlier to layout the nodes - const nodeObjects = dataToObjects(data); - setElements(layoutedElements); - setConnections(connections); - setNodeObjects(nodeObjects); - console.log("nodeObjects", nodeObjects); - console.log("connection", connections); - console.log("server message received ", data) - - return () => { - } - }, []); - - const onLayout = useCallback( - (direction) => { - const layoutedElements = getLayoutedElements(elements, direction); - setElements(layoutedElements); - }, - [elements] - ); - - return( -
-
-
-
- - -
- - - - - - - - - - - - - -
NodeType
{node_name}{type}
-
- -
- - - - - -
- -
- - - ) -}; - -export default InteractionGraph; \ No newline at end of file diff --git a/extensions/prototypes/labgraph_monitor/helper.js b/extensions/prototypes/labgraph_monitor/helper.js deleted file mode 100644 index d2a9d859e..000000000 --- a/extensions/prototypes/labgraph_monitor/helper.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - -Copyright (c) Facebook, Inc. and its affiliates. -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ - -/** - * Converts the data from server to connections array - * @param {Array} data - data received from server through websocket - * @return {Array} An array having arrays of connections - */ - function dataToConnections(data){ - const connections = {} - for (const array of data) { - for (let index = 1; index < array.length; index++){ - const parent = Object.keys(array[0])[0] - const childNode = Object.keys(array[index])[0] - if (connections[parent]){ - connections[parent].push(childNode) - } else{ - connections[parent] = [childNode] - } - } - } - return connections -} - -/** - * Converts the data from server to Objects array of the nodes - * @param {Array} data - data received from server through websocket - * @return {Array} An array having arrays of Objects representation of data from server. - * Every object represents a node and has properties such as inputs, outputs, config.. for that node - */ - function dataToObjects(data){ - const objects = {} - for (const array of data) { - const parent = array[0] - objects[Object.keys(parent)[0]] = parent[Object.keys(parent)[0]] - for (let index = 1; index < array.length; index++){ - const childNode = array[index] - objects[Object.keys(childNode)[0]] = childNode[Object.keys(childNode)[0]] - } - } - return objects -} - -/** - * Converts the data from server to connections array - * @param {Array} connections - connections between nodes generated from server data - * @return {Array} Elements array having Nodes for graph representation. A sample graph Node has id, data and position - */ - function connectionsToNodes(connections){ - const adjacencyListKeys = Object.keys(connections) - console.log("adjacency list keys", adjacencyListKeys) - console.log("connections are" , connections) - // adding all nodes of the graph without duplicates - const allNodes = Object.keys(connections) - for (let [, edgeNodes] of Object.entries(connections)) { - for (let n of edgeNodes) { - if (!allNodes.includes(n)) { - allNodes.push(n) - } - } - } - // generating the elements list having nodes with their properties - const elements = [] - for (let node of allNodes) { - elements.push({ - id: node, - data: { label: node }, - position: 0, - }) - } - - const elements_index = {} - - for (let i = 0; i < elements.length; i++) { - const node = elements[i].id - elements_index[node] = i - } - - // generating the edges between every source and target node - for (let i = 0; i < adjacencyListKeys.length; i++) { - const node = adjacencyListKeys[i] - const edgeNodes = connections[node] - let j = -1 - for (let edgeNode of edgeNodes) { - elements.push({ id: `e-${node}-${edgeNode}`, source: node, target: edgeNode, animated: true },) - j ++ - } - } - - console.log(elements) - - return elements - } - -module.exports = { - dataToConnections, - dataToObjects, - connectionsToNodes, -} - diff --git a/extensions/prototypes/labgraph_monitor/package.json b/extensions/prototypes/labgraph_monitor/package.json new file mode 100644 index 000000000..decb87738 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/package.json @@ -0,0 +1,80 @@ +{ + "name": "labgraph_monitor", + "version": "0.1.0", + "private": true, + "dependencies": { + "@babel/core": "^7.17.2", + "@emotion/react": "^11.7.1", + "@emotion/styled": "^11.6.0", + "@mui/icons-material": "^5.4.1", + "@mui/lab": "^5.0.0-alpha.68", + "@mui/material": "^5.4.1", + "@mui/styles": "^5.4.1", + "@reduxjs/toolkit": "^1.7.2", + "@testing-library/jest-dom": "^5.14.1", + "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^13.2.1", + "@types/jest": "^27.4.0", + "@types/lodash": "^4.14.178", + "@types/node": "^16.7.13", + "@types/react": "^17.0.20", + "@types/react-dom": "^17.0.9", + "@types/socket.io-client": "^3.0.0", + "@types/websocket": "^1.0.5", + "dagre": "^0.8.5", + "lodash": "^4.17.21", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-flow-renderer": "^9.7.4", + "react-redux": "^7.2.6", + "react-scripts": "5.0.0", + "resize-observer-polyfill": "^1.5.1", + "socket.io-client": "^4.4.1", + "typescript": "^4.4.2", + "web-vitals": "^2.1.0", + "websocket": "^1.0.34" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject", + "format": "prettier --write .", + "lint": "eslint ./src --ext .ts --ext .tsx --max-warnings=0", + "check-code": "yarn run lint && yarn run format", + "prepare": "cd ../../.. && husky install extensions/prototypes/labgraph_monitor/.husky" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@types/dagre": "^0.7.47", + "@typescript-eslint/eslint-plugin": "^5.11.0", + "@typescript-eslint/parser": "^5.11.0", + "eslint": "^8.8.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0", + "husky": "^7.0.4", + "prettier": "2.5.1" + } +} diff --git a/extensions/prototypes/labgraph_monitor/public/favicon.ico b/extensions/prototypes/labgraph_monitor/public/favicon.ico new file mode 100644 index 000000000..536909711 Binary files /dev/null and b/extensions/prototypes/labgraph_monitor/public/favicon.ico differ diff --git a/extensions/prototypes/labgraph_monitor/public/index.html b/extensions/prototypes/labgraph_monitor/public/index.html new file mode 100644 index 000000000..8b28b36c8 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/public/index.html @@ -0,0 +1,24 @@ + + + + + + + + + + + Labgraph Monitor + + + +
+ + diff --git a/extensions/prototypes/labgraph_monitor/public/manifest.json b/extensions/prototypes/labgraph_monitor/public/manifest.json new file mode 100644 index 000000000..361b4cb67 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Labgraph Monitor", + "name": "This app is an interactive visualization tool to monitor and make real-time changes to LabGraph graph and nodes.", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/extensions/prototypes/labgraph_monitor/public/robots.txt b/extensions/prototypes/labgraph_monitor/public/robots.txt new file mode 100644 index 000000000..eb0536286 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/extensions/prototypes/labgraph_monitor/src/App.tsx b/extensions/prototypes/labgraph_monitor/src/App.tsx new file mode 100644 index 000000000..d431257a3 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/App.tsx @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { Home } from './pages'; + +/** + * The main component of the application + * @returns {JSX.Element} + */ +const App: React.FC = (): JSX.Element => { + return ( +
+ +
+ ); +}; + +export default App; diff --git a/extensions/prototypes/labgraph_monitor/src/components/Graph/Graph.tsx b/extensions/prototypes/labgraph_monitor/src/components/Graph/Graph.tsx new file mode 100644 index 000000000..5e802d649 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/Graph/Graph.tsx @@ -0,0 +1,154 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { useEffect, useState } from 'react'; +import ReactFlow, { + ReactFlowProvider, + Controls, + Background, + Edge, + Node, + isNode, + isEdge, +} from 'react-flow-renderer'; +import { TGraphElement } from './types/TGraphElement'; +import { useUIContext } from '../../contexts'; +import { layoutGraph } from './util/layoutGraph'; +import { RootState } from '../../redux/store'; +import { useSelector, useDispatch } from 'react-redux'; +import { + setPanel, + setTabIndex, + setSelectedNode, + setSelectedEdge, +} from '../../redux/reducers/config/configReducer'; +import WS_STATE from '../../redux/reducers/graph/ws/enums/WS_STATE'; +import { Box } from '@mui/material'; +import { makeStyles } from '@mui/styles'; + +const useStyles = makeStyles({ + node: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '150px', + height: '150px', + borderRadius: '50%', + fontWeight: '500', + fill: 'currentColor', + }, + + edge: { + stroke: 'inherit', + }, + + controllers: { + position: 'absolute', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + bottom: '40px', + left: '40px', + cursor: 'pointer', + }, +}); + +/** + * A component that represents LabGraph computational graph. + * + * @returns {JSX.Element} + */ +const Graph: React.FC = (): JSX.Element => { + const styles = useStyles(); + const { layout } = useUIContext(); + const { mockGraph } = useSelector((state: RootState) => state.mock); + + const { connection, graph: realtimeGraph } = useSelector( + (state: RootState) => state.ws + ); + + const serializedGraph = + connection === WS_STATE.CONNECTED ? realtimeGraph : mockGraph; + + const [graph, setGraph] = useState>( + [] as Array + ); + + const dispatch = useDispatch(); + + useEffect(() => { + const { nodes } = serializedGraph; + const deserializedGraph = [] as Array; + if (!nodes) return; + for (const [name, data] of Object.entries(nodes)) { + deserializedGraph.push({ + id: name, + data: { + label: name, + }, + position: { x: 0, y: 0 }, + targetPosition: layout === 'horizontal' ? 'left' : 'top', + sourcePosition: layout === 'horizontal' ? 'right' : 'bottom', + className: styles.node, + }); + + Object.entries(data.upstreams).forEach(([upstream, messages]) => { + messages.forEach((message) => { + deserializedGraph.push({ + id: `e${upstream}-${name}`, + label: `${message.name}`, + source: upstream, + target: name, + arrowHeadType: 'arrow', + type: 'default', + animated: Object.keys(nodes[upstream].upstreams).length + ? false + : true, + className: styles.edge, + }); + }); + }); + } + setGraph(layoutGraph(deserializedGraph, layout)); + }, [serializedGraph, layout, styles]); + + const handleElementClick = ( + event: React.MouseEvent, + element: Node | Edge + ) => { + if (isNode(element)) { + dispatch(setPanel(true)); + dispatch(setTabIndex('2')); + dispatch(setSelectedNode(element)); + } else if (isEdge(element)) { + dispatch(setPanel(true)); + dispatch(setTabIndex('3')); + dispatch(setSelectedEdge(element)); + } + }; + + return ( + + + + + + + + + ); +}; + +export default Graph; diff --git a/extensions/prototypes/labgraph_monitor/src/components/Graph/__test__/Graph.test.tsx b/extensions/prototypes/labgraph_monitor/src/components/Graph/__test__/Graph.test.tsx new file mode 100644 index 000000000..ae66bdd07 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/Graph/__test__/Graph.test.tsx @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { render, screen } from '@testing-library/react'; +import { store } from '../../../redux/store'; +import { Provider } from 'react-redux'; +import Graph from '../Graph'; + +const MockGraph = () => { + return ( + + + + ); +}; + +describe('Graph', () => { + it('should render Graph component', async () => { + render(); + const graph = screen.getByTestId('graph'); + expect(graph).toBeInTheDocument(); + }); +}); diff --git a/extensions/prototypes/labgraph_monitor/src/components/Graph/interfaces/IEdge.ts b/extensions/prototypes/labgraph_monitor/src/components/Graph/interfaces/IEdge.ts new file mode 100644 index 000000000..9a6ec67e1 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/Graph/interfaces/IEdge.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +interface IEdge { + id: string; + label?: string; + source: string; + target: string; + arrowHeadType?: 'arrow' | 'arrowclosed'; + type?: 'default' | 'straight' | 'step' | 'smoothstep'; + animated?: boolean; + style?: { [property: string]: string }; + className?: string; +} + +export default IEdge; diff --git a/extensions/prototypes/labgraph_monitor/src/components/Graph/interfaces/INode.ts b/extensions/prototypes/labgraph_monitor/src/components/Graph/interfaces/INode.ts new file mode 100644 index 000000000..1aca15137 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/Graph/interfaces/INode.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +interface INode { + id: string; + data?: { + label: string; + }; + position: { + x: number; + y: number; + }; + sourcePosition: 'left' | 'top' | 'right' | 'bottom'; + targetPosition: 'left' | 'top' | 'right' | 'bottom'; + style?: { [property: string]: string }; + className?: string; +} + +export default INode; diff --git a/extensions/prototypes/labgraph_monitor/src/components/Graph/types/TGraphElement.ts b/extensions/prototypes/labgraph_monitor/src/components/Graph/types/TGraphElement.ts new file mode 100644 index 000000000..75cfa4ccb --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/Graph/types/TGraphElement.ts @@ -0,0 +1,9 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import IEdge from '../interfaces/IEdge'; +import INode from '../interfaces/INode'; +export type TGraphElement = INode | IEdge; diff --git a/extensions/prototypes/labgraph_monitor/src/components/Graph/util/layoutGraph.ts b/extensions/prototypes/labgraph_monitor/src/components/Graph/util/layoutGraph.ts new file mode 100644 index 000000000..89d7b21f0 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/Graph/util/layoutGraph.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import dagre from 'dagre'; +import { isNode, isEdge } from 'react-flow-renderer'; +import { TGraphElement } from '../types/TGraphElement'; + +const dagreGraph = new dagre.graphlib.Graph(); +dagreGraph.setDefaultEdgeLabel(() => ({})); + +/** + * A function used to update the graph layout + * + * @param {Array, + layout: string +): Array => { + dagreGraph.setGraph({ rankdir: layout === 'horizontal' ? 'LR' : 'TB' }); + elements.forEach((el: any) => { + if (isNode(el)) { + dagreGraph.setNode(el.id, { width: 400, height: 200 }); + } else if (isEdge(el)) { + dagreGraph.setEdge(el.source, el.target); + } + }); + + dagre.layout(dagreGraph); + + return elements.map((el: any) => { + if (isNode(el)) { + const { x, y } = dagreGraph.node(el.id); + el.position = { x, y }; + } + + return el; + }); +}; diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/EdgeSettings.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/EdgeSettings.tsx new file mode 100644 index 000000000..be5c4f749 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/EdgeSettings.tsx @@ -0,0 +1,95 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { + Box, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from '@mui/material'; +import React from 'react'; +import { RootState } from '../../redux/store'; +import { useSelector } from 'react-redux'; +import WS_STATE from '../../redux/reducers/graph/ws/enums/WS_STATE'; + +interface IMessage { + name: string; + fields: { [fieldName: string]: string }; +} + +/** + * A component that manages the settings of an edge. + * All components related to edge settings should be children of this component. + * + * @returns {JSX.Element} + */ +const Edge: React.FC = (): JSX.Element => { + const { selectedEdge } = useSelector((state: RootState) => state.config); + const { connection, graph: realtimeGraph } = useSelector( + (state: RootState) => state.ws + ); + const { mockGraph } = useSelector((state: RootState) => state.mock); + + const graph = connection === WS_STATE.CONNECTED ? realtimeGraph : mockGraph; + + const messages: IMessage[] = + graph && selectedEdge.target + ? graph['nodes'][selectedEdge.target]['upstreams'][ + selectedEdge.source + ] + : []; + return ( + + + {messages.map((message: IMessage, index) => { + return ( + + + + + {message.name} + + + + {Object.entries(message.fields).map( + ([name, type]) => { + return ( + + + {name} + + + {type} + + + ); + } + )} + +
+
+ ); + })} + + {!messages.length && ( + + Click on an edge to see the message information + + )} +
+
+ ); +}; + +export default Edge; diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/GraphSettings.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/GraphSettings.tsx new file mode 100644 index 000000000..f09924992 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/GraphSettings.tsx @@ -0,0 +1,187 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { useCallback, useState } from 'react'; +import { TabContext, TabList, TabPanel } from '@mui/lab'; +import { + Box, + Tab, + MenuItem, + InputLabel, + FormControl, + Button, +} from '@mui/material'; +import { makeStyles } from '@mui/styles'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import { MOCK } from '../../mocks'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../redux/store'; +import { setMockGraph } from '../../redux/reducers/graph/mock/mockReducer'; +import { setConnection } from '../../redux/reducers/graph/ws/WSReducer'; +import WS_STATE from '../../redux/reducers/graph/ws/enums/WS_STATE'; + +const useStyles = makeStyles({ + root: { + width: '100%', + }, + + tab: { + width: '50%', + }, + tabLabel: { + fontSize: '.85rem', + fontWeight: 400, + }, + + textFieldLabel: { + fontSize: '.85rem', + }, +}); + +/** + * A component that manages the settings of a graph. + * + * @returns {JSX.Element} + */ +const GraphSettings: React.FC = (): JSX.Element => { + const classes = useStyles(); + const { connection } = useSelector((state: RootState) => state.ws); + const [tabIndex, setTabIndex] = useState('1'); + const [mock, setMock] = useState(''); + const dispatch = useDispatch(); + + const toggleTab = (_: React.SyntheticEvent, newValue: string) => { + setTabIndex(newValue); + }; + + const handleMockChange = (event: SelectChangeEvent) => { + dispatch(setMockGraph(event.target.value)); + setMock(event.target.value); + }; + + const handleConnect = (event: React.FormEvent) => { + event.preventDefault(); + dispatch(setConnection(WS_STATE.IS_CONNECTING)); + }; + + const handleDisconnect = (event: React.FormEvent) => { + event.preventDefault(); + dispatch(setConnection(WS_STATE.IS_DISCONNECTING)); + }; + + const renderConnectionButton = useCallback(() => { + switch (connection) { + case WS_STATE.IS_CONNECTING: + return ( + + ); + case WS_STATE.CONNECTED: + return ( + + ); + case WS_STATE.IS_DISCONNECTING: + return ( + + ); + case WS_STATE.DISCONNECTED: + return ( + + ); + } + }, [connection]); + + return ( + + + + + + realtime + + } + value="1" + /> + mock + } + value="2" + /> + + + + +
+ + {renderConnectionButton()} + +
+
+
+ + + + + Mock + + + + + +
+
+ ); +}; + +export default GraphSettings; diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/NodeSettings.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/NodeSettings.tsx new file mode 100644 index 000000000..5c4b8e948 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/NodeSettings.tsx @@ -0,0 +1,36 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import React from 'react'; +import { RootState } from '../../redux/store'; +import { useSelector } from 'react-redux'; +import { Box, Typography } from '@mui/material'; + +/** + * A component that manages the settings of a node. + * All components related to node settings should be children of this component. + * + * @returns {JSX.Element} + */ +const Node: React.FC = (): JSX.Element => { + const { selectedNode } = useSelector((state: RootState) => state.config); + + return ( + + + {selectedNode?.id ? ( + {selectedNode.id} + ) : ( + + Click on a node to see its information + + )} + + + ); +}; + +export default Node; diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/SettingPanel.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/SettingPanel.tsx new file mode 100644 index 000000000..2bc3b7a9d --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/SettingPanel.tsx @@ -0,0 +1,139 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { Box, Drawer, CssBaseline, IconButton, Divider } from '@mui/material'; +import { + ChevronRight, + Brightness7, + DarkMode, + SettingsApplicationsRounded, + AlignHorizontalLeftOutlined, + AlignVerticalTopOutlined, +} from '@mui/icons-material'; +import SettingTabs from './SettingTabs'; +import { makeStyles } from '@mui/styles'; +import { useUIContext } from '../../contexts'; +import { RootState } from '../../redux/store'; +import { useSelector, useDispatch } from 'react-redux'; +import { setPanel } from '../../redux/reducers/config/configReducer'; + +const PANEL_WIDTH = 280; + +const useStyles = makeStyles({ + root: { + display: 'flex', + }, + + settingButton: { + position: 'absolute', + top: '10px', + right: '20px', + zIndex: '10', + }, + + settingIcon: { + '&:hover': { + opacity: 0.9, + }, + }, + + settingPanel: { + width: PANEL_WIDTH, + flexShrink: 0, + '& .MuiDrawer-paper': { + width: PANEL_WIDTH, + }, + }, + + themeBar: { + display: 'flex', + width: '100%', + justifyContent: 'space-around', + alignItem: 'center', + padding: '2px 4px 2px 4px', + }, +}); + +/** + * A component that represents the setting panel of the application. + * All components related to UI or Graph settings should be children of this component. + * + * @returns {JSX.Element} + */ +const SettingPanel: React.FC = (): JSX.Element => { + const classes = useStyles(); + const { mode, layout, toggleMode, toggleLayout } = useUIContext(); + const { panelOpen } = useSelector((state: RootState) => state.config); + const dispatch = useDispatch(); + + return ( + + + dispatch(setPanel(true))} + className={classes.settingButton} + data-testid="open-setting-btn" + sx={{ + display: panelOpen ? 'none' : 'block', + position: 'absolute', + '&:hover, &.Mui-focusVisible': { + backgroundColor: 'transparent', + }, + }} + > + + + + + dispatch(setPanel(false))} + data-testid="close-setting-btn" + > + + + + + + + {mode === 'light' ? ( + + ) : ( + + )} + + + {layout === 'horizontal' ? ( + + ) : ( + + )} + + + + + + + ); +}; + +export default SettingPanel; diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/SettingTabs.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/SettingTabs.tsx new file mode 100644 index 000000000..1ab14f926 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/SettingTabs.tsx @@ -0,0 +1,66 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { TabContext, TabList, TabPanel } from '@mui/lab'; +import { Box, Tab } from '@mui/material'; +import { makeStyles } from '@mui/styles'; +import NodeSettings from './NodeSettings'; +import EdgeSettings from './EdgeSettings'; +import GraphSettings from './GraphSettings'; +import { RootState } from '../../redux/store'; +import { useSelector, useDispatch } from 'react-redux'; +import { setTabIndex } from '../../redux/reducers/config/configReducer'; + +const useStyles = makeStyles({ + root: { + width: '100%', + }, +}); + +/** + * A component that categorizes settings within the settings panel + * + * @returns {JSX.Element} + */ +const SettingTabs: React.FC = (): JSX.Element => { + const classes = useStyles(); + const { tabIndex } = useSelector((state: RootState) => state.config); + const dispatch = useDispatch(); + return ( + + + + + dispatch(setTabIndex(newValue)) + } + data-testid="tab-list" + > + + + + + + + + + + + + + + + + + ); +}; + +export default SettingTabs; diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/EdgeSettings.test.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/EdgeSettings.test.tsx new file mode 100644 index 000000000..4cc46044b --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/EdgeSettings.test.tsx @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { render, screen } from '@testing-library/react'; +import { store } from '../../../redux/store'; +import { Provider } from 'react-redux'; +import EdgeSettings from '../EdgeSettings'; + +const MockEdgeSettings = () => { + return ( + + + + ); +}; + +describe('EdgeSettings', () => { + it('should render EdgeSettings component', async () => { + render(); + const edgeSettings = screen.getByTestId('edge-settings'); + expect(edgeSettings).toBeInTheDocument(); + }); +}); diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/GraphSettings.test.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/GraphSettings.test.tsx new file mode 100644 index 000000000..b0f9490eb --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/GraphSettings.test.tsx @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { render, screen } from '@testing-library/react'; +import { store } from '../../../redux/store'; +import { Provider } from 'react-redux'; +import GraphSettings from '../GraphSettings'; + +const MockGraphSettings = () => { + return ( + + + + ); +}; + +describe('GraphSettings', () => { + it('should render GraphSettings component', async () => { + render(); + const graphSettings = screen.getByTestId('graph-settings'); + expect(graphSettings).toBeInTheDocument(); + }); +}); diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/NodeSettings.test.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/NodeSettings.test.tsx new file mode 100644 index 000000000..f208c438e --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/NodeSettings.test.tsx @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { render, screen } from '@testing-library/react'; +import { store } from '../../../redux/store'; +import { Provider } from 'react-redux'; +import NodeSettings from '../NodeSettings'; + +const MockNodeSettings = () => { + return ( + + + + ); +}; + +describe('NodeSettings', () => { + it('should render NodeSettings component', async () => { + render(); + const nodeSettings = screen.getByTestId('node-settings'); + expect(nodeSettings).toBeInTheDocument(); + }); +}); diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/SettingPanel.test.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/SettingPanel.test.tsx new file mode 100644 index 000000000..8b0b6a211 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/SettingPanel.test.tsx @@ -0,0 +1,48 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { render, screen, fireEvent } from '@testing-library/react'; +import { store } from '../../../redux/store'; +import { Provider } from 'react-redux'; +import SettingPanel from '../SettingPanel'; + +const MockSettingPanel = () => { + return ( + + + + ); +}; + +describe('SettingPanel', () => { + it('should render SettingPanel component', async () => { + render(); + const settingPanel = screen.getByTestId('setting-panel'); + expect(settingPanel).toBeInTheDocument(); + }); + + it('should toggle mode', async () => { + render(); + const toggleModeBtn = screen.getByTestId('toggle-mode-btn'); + fireEvent.click(toggleModeBtn); + const lightModeIcon = screen.getByTestId('light-mode-icon'); + const darkModeIcon = screen.queryByText('dark-mode-icon'); + expect(lightModeIcon).toBeInTheDocument(); + expect(darkModeIcon).toBeNull(); + }); + + it('should toggle layout', async () => { + render(); + const toggleLayoutBtn = screen.getByTestId('toggle-layout-btn'); + fireEvent.click(toggleLayoutBtn); + const horizontalLayoutIcon = screen.getByTestId( + 'horizontal-layout-icon' + ); + const verticalLayoutIcon = screen.queryByText('vertical-layout-icon'); + expect(horizontalLayoutIcon).toBeInTheDocument(); + expect(verticalLayoutIcon).toBeNull(); + }); +}); diff --git a/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/SettingTabs.test.tsx b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/SettingTabs.test.tsx new file mode 100644 index 000000000..55ea14df4 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/SettingPanel/__test__/SettingTabs.test.tsx @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { render, screen } from '@testing-library/react'; +import { store } from '../../../redux/store'; +import { Provider } from 'react-redux'; +import SettingTabs from '../SettingTabs'; + +const MockSettingTabs = () => { + return ( + + + + ); +}; + +describe('SettingTabs', () => { + it('should render SettingTabs component', async () => { + render(); + const settingPanel = screen.getByTestId('setting-tabs'); + expect(settingPanel).toBeInTheDocument(); + }); +}); diff --git a/extensions/prototypes/labgraph_monitor/src/components/index.ts b/extensions/prototypes/labgraph_monitor/src/components/index.ts new file mode 100644 index 000000000..1aa3e9711 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/components/index.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +export { default as SettingPanel } from './SettingPanel/SettingPanel'; +export { default as Graph } from './Graph/Graph'; diff --git a/extensions/prototypes/labgraph_monitor/src/contexts/UIContext/UIContext.tsx b/extensions/prototypes/labgraph_monitor/src/contexts/UIContext/UIContext.tsx new file mode 100644 index 000000000..cd520f5fe --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/contexts/UIContext/UIContext.tsx @@ -0,0 +1,114 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { + ReactNode, + useState, + useEffect, + createContext, + useContext, + useMemo, +} from 'react'; +import { createTheme, ThemeProvider } from '@mui/material/styles'; +import IUIContext from './interfaces/IUIContext'; + +const UIContext = createContext({} as IUIContext); +export const useUIContext = (): IUIContext => useContext(UIContext); + +/** + * A context component used to share UI related information with different component + * + * @param {ReactNode} props represents the react components wrapped by this context + * @returns {JSX.Element} + */ +const UIContextProvider: React.FC = ({ children }): JSX.Element => { + const localMode = localStorage.getItem('labgraph_monitor_mode') as + | 'dark' + | 'light'; + + const [mode, setMode] = useState<'light' | 'dark'>(() => { + return localMode ? localMode : 'dark'; + }); + const [layout, setLayout] = useState<'horizontal' | 'vertical'>( + 'horizontal' + ); + + useEffect(() => { + return () => { + localStorage.setItem( + 'labgraph_monitor_mode', + mode === 'dark' ? 'light' : 'dark' + ); + }; + }, [mode]); + + const themeObject = useMemo( + () => ({ + toggleMode: () => { + setMode((prevMode) => + prevMode === 'light' ? 'dark' : 'light' + ); + }, + mode, + }), + [mode] + ); + + const layoutObject = useMemo( + () => ({ + toggleLayout: () => { + setLayout((prevLayout) => + prevLayout === 'horizontal' ? 'vertical' : 'horizontal' + ); + }, + layout, + }), + [layout] + ); + + const theme = useMemo( + () => + createTheme({ + palette: { + mode, + ...(mode === 'dark' + ? { + background: { + default: '#111827', + paper: '#1B2533', + }, + text: { + primary: '#F7F7F7', + secondary: '#FFF', + }, + } + : { + background: { + default: '#FEFEFE', + paper: '#FFF', + }, + text: { + primary: '#111827', + }, + }), + }, + }), + [mode] + ); + + return ( + + {children} + + ); +}; + +export default UIContextProvider; diff --git a/extensions/prototypes/labgraph_monitor/src/contexts/UIContext/interfaces/IUIContext.ts b/extensions/prototypes/labgraph_monitor/src/contexts/UIContext/interfaces/IUIContext.ts new file mode 100644 index 000000000..b67d5feda --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/contexts/UIContext/interfaces/IUIContext.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +interface IUIContext { + mode: 'light' | 'dark'; + toggleMode: () => void; + layout: 'horizontal' | 'vertical'; + toggleLayout: () => void; +} + +export default IUIContext; diff --git a/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/WSContext.tsx b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/WSContext.tsx new file mode 100644 index 000000000..c640a8ff6 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/WSContext.tsx @@ -0,0 +1,83 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { ReactNode, useRef, createContext, useEffect } from 'react'; +import { w3cwebsocket as W3CWebSocket } from 'websocket'; +import { RootState } from '../../redux/store'; +import { useSelector, useDispatch } from 'react-redux'; +import { + setConnection, + setGraph, +} from '../../redux/reducers/graph/ws/WSReducer'; +import { copyRealtimeGraph } from '../../redux/reducers/graph/mock/mockReducer'; +import WS_STATE from '../../redux/reducers/graph/ws/enums/WS_STATE'; +import startStreamRequest from './json/startStreamRequest.json'; +import endStreamRequest from './json/endStreamRequest.json'; +import _ from 'lodash'; + +const GraphContext = createContext<{}>({}); + +/** + * A context component used to share WebSocket related information with different component. + * All interactions with the LabGraph WebSockets API are happening inside this context. + * + * @param {ReactNode} props represents the react components wrapped by this context + * @returns {JSX.Element} + */ +const WSContextProvider: React.FC = ({ children }): JSX.Element => { + const { connection, graph } = useSelector((state: RootState) => state.ws); + + const clientRef = useRef(null); + const dispatch = useDispatch(); + + useEffect(() => { + if (!process.env.REACT_APP_WS_API) return; + switch (connection) { + case WS_STATE.IS_CONNECTING: + clientRef.current = new W3CWebSocket( + process.env.REACT_APP_WS_API as string + ); + + clientRef.current.onopen = () => { + clientRef.current?.send(JSON.stringify(startStreamRequest)); + dispatch(setConnection(WS_STATE.CONNECTED)); + }; + + clientRef.current.onerror = (err: any) => { + dispatch(setConnection(WS_STATE.DISCONNECTED)); + }; + + break; + + case WS_STATE.CONNECTED: + if (!clientRef.current) return; + clientRef.current.onmessage = (message: any) => { + const data = JSON.parse(message.data); + if (!data['stream_batch']) return; + const { + stream_batch: { + 'labgraph.monitor': { samples }, + }, + } = data; + if (!_.isEqual(samples[0]['data'], graph)) { + dispatch(setGraph(samples[0]['data'])); + } + }; + + break; + + case WS_STATE.IS_DISCONNECTING: + clientRef.current?.send(JSON.stringify(endStreamRequest)); + clientRef.current?.close(); + dispatch(setConnection(WS_STATE.DISCONNECTED)); + dispatch(copyRealtimeGraph(graph)); + } + }, [connection, graph, dispatch]); + + return {children}; +}; + +export default WSContextProvider; diff --git a/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/interfaces/IGraph.ts b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/interfaces/IGraph.ts new file mode 100644 index 000000000..3b53d7c7b --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/interfaces/IGraph.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import INode from './INode'; + +interface IGraph { + name: string; + nodes: INode; +} + +export default IGraph; diff --git a/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/interfaces/INode.ts b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/interfaces/INode.ts new file mode 100644 index 000000000..1e4e58895 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/interfaces/INode.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +interface INode { + [name: string]: { + upstreams: { + [upstream: string]: Array<{ + name: string; + fields: { + [fieldName: string]: string; + }; + }>; + }; + }; +} + +export default INode; diff --git a/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/interfaces/IWSContext.ts b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/interfaces/IWSContext.ts new file mode 100644 index 000000000..75711857e --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/interfaces/IWSContext.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { Dispatch, SetStateAction } from 'react'; +import IGraph from './IGraph'; + +interface IWSContext { + graph: IGraph; + mock: string; + setMock: Dispatch>; + endPoint: string; + setEndPoint: Dispatch>; + isConnected: boolean; + setIsConnected: Dispatch>; +} + +export default IWSContext; diff --git a/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/json/endStreamRequest.json b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/json/endStreamRequest.json new file mode 100644 index 000000000..4bd50588c --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/json/endStreamRequest.json @@ -0,0 +1,10 @@ +{ + "api_version": "0.1", + "api_request": { + "request_id": 1, + "end_stream_request": { + "stream_id": "LABGRAPH.MONITOR", + "labgraph.monitor": {} + } + } +} diff --git a/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/json/startStreamRequest.json b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/json/startStreamRequest.json new file mode 100644 index 000000000..0cb6b1d90 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/contexts/WSContext/json/startStreamRequest.json @@ -0,0 +1,10 @@ +{ + "api_version": "0.1", + "api_request": { + "request_id": 1, + "start_stream_request": { + "stream_id": "LABGRAPH.MONITOR", + "labgraph.monitor": {} + } + } +} diff --git a/extensions/prototypes/labgraph_monitor/src/contexts/index.ts b/extensions/prototypes/labgraph_monitor/src/contexts/index.ts new file mode 100644 index 000000000..e1621c451 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/contexts/index.ts @@ -0,0 +1,9 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +export { default as WSContextProvider } from './WSContext/WSContext'; +export { default as UIContextProvider } from './UIContext/UIContext'; +export { useUIContext } from './UIContext/UIContext'; diff --git a/extensions/prototypes/labgraph_monitor/src/index.tsx b/extensions/prototypes/labgraph_monitor/src/index.tsx new file mode 100644 index 000000000..1e8e641fc --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/index.tsx @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; +import { UIContextProvider } from './contexts'; +import reportWebVitals from './reportWebVitals'; +import './statics/css/global.css'; +import { store } from './redux/store'; +import { Provider } from 'react-redux'; + +ReactDOM.render( + + + + + + + , + document.getElementById('root') +); + +reportWebVitals(); diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/enums/MOCK.ts b/extensions/prototypes/labgraph_monitor/src/mocks/enums/MOCK.ts new file mode 100644 index 000000000..2d4a5ca60 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/mocks/enums/MOCK.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +enum MOCK { + SIMPLE_VIZ = 'simple_viz', + SIMPLE_VIZ_ZMQ = 'simple_viz_zmq', + SIMPLE_VIZ_FIXED_RATE = 'simple_viz_fixed_rate', + SIMULATION = 'simulation', + DEMO = 'demo', +} + +export default MOCK; diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/index.ts b/extensions/prototypes/labgraph_monitor/src/mocks/index.ts new file mode 100644 index 000000000..8a5fd7bfb --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/mocks/index.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +export { selectMock } from './util/selectMock'; +export { default as MOCK } from './enums/MOCK'; diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/json/demo.json b/extensions/prototypes/labgraph_monitor/src/mocks/json/demo.json new file mode 100644 index 000000000..868386e20 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/mocks/json/demo.json @@ -0,0 +1,92 @@ +{ + "stream_batch": { + "stream_id": "LABGRAPH.MONITOR", + "labgraph.monitor": { + "samples": [ + { + "data": { + "name": "Demo", + "nodes": { + "NoiseGenerator": { + "upstreams": {} + }, + "RollingAverager": { + "upstreams": { + "NoiseGenerator": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ] + } + }, + "Amplifier": { + "upstreams": { + "NoiseGenerator": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ] + } + }, + "Attenuator": { + "upstreams": { + "NoiseGenerator": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ] + } + }, + "Sink": { + "upstreams": { + "RollingAverager": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ], + "Amplifier": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ], + "Attenuator": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ] + } + } + } + }, + "produced_timestamp_s": 1644931422.141309, + "timestamp_s": 1644931422.141309 + } + ], + "batch_num": 54 + } + } +} diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz.json b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz.json new file mode 100644 index 000000000..cb09e6846 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz.json @@ -0,0 +1,48 @@ +{ + "stream_batch": { + "stream_id": "LABGRAPH.MONITOR", + "labgraph.monitor": { + "samples": [ + { + "data": { + "name": "Demo", + "nodes": { + "Plot": { + "upstreams": { + "RollingAverager": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ] + } + }, + "NoiseGenerator": { + "upstreams": {} + }, + "RollingAverager": { + "upstreams": { + "NoiseGenerator": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ] + } + } + } + }, + "produced_timestamp_s": 1644930793.478768, + "timestamp_s": 1644930793.478768 + } + ], + "batch_num": 132 + } + } +} diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_fixed_rate.json b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_fixed_rate.json new file mode 100644 index 000000000..efdd93bf8 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_fixed_rate.json @@ -0,0 +1,48 @@ +{ + "stream_batch": { + "stream_id": "LABGRAPH.MONITOR", + "labgraph.monitor": { + "samples": [ + { + "data": { + "name": "Demo", + "nodes": { + "Plot": { + "upstreams": { + "RollingAverager": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ] + } + }, + "NoiseGenerator": { + "upstreams": {} + }, + "RollingAverager": { + "upstreams": { + "NoiseGenerator": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ] + } + } + } + }, + "produced_timestamp_s": 1644930955.5364213, + "timestamp_s": 1644930955.5364213 + } + ], + "batch_num": 94 + } + } +} diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_zmq.json b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_zmq.json new file mode 100644 index 000000000..fd54835af --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/mocks/json/simple_viz_zmq.json @@ -0,0 +1,60 @@ +{ + "stream_batch": { + "stream_id": "LABGRAPH.MONITOR", + "labgraph.monitor": { + "samples": [ + { + "data": { + "name": "Demo", + "nodes": { + "Plot": { + "upstreams": { + "RollingAverager": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ] + } + }, + "ZMQPollerNode": { + "upstreams": {} + }, + "ZMQDeserializer": { + "upstreams": { + "ZMQPollerNode": [ + { + "name": "ZMQMessage", + "fields": { + "data": "bytes" + } + } + ] + } + }, + "RollingAverager": { + "upstreams": { + "ZMQDeserializer": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": "float", + "data": "ndarray" + } + } + ] + } + } + } + }, + "produced_timestamp_s": 1644931059.3961816, + "timestamp_s": 1644931059.3961816 + } + ], + "batch_num": 132 + } + } +} diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/json/simulation.json b/extensions/prototypes/labgraph_monitor/src/mocks/json/simulation.json new file mode 100644 index 000000000..87856e25f --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/mocks/json/simulation.json @@ -0,0 +1,35 @@ +{ + "stream_batch": { + "stream_id": "LABGRAPH.MONITOR", + "labgraph.monitor": { + "samples": [ + { + "data": { + "name": "Demo", + "nodes": { + "SimulationGenerator": { + "upstreams": {} + }, + "Plot": { + "upstreams": { + "SimulationGenerator": [ + { + "name": "SimulationMessage", + "fields": { + "timestamp": "ndarray", + "daub_data": "ndarray" + } + } + ] + } + } + } + }, + "produced_timestamp_s": 1644931259.3396878, + "timestamp_s": 1644931259.3396878 + } + ], + "batch_num": 24 + } + } +} diff --git a/extensions/prototypes/labgraph_monitor/src/mocks/util/selectMock.ts b/extensions/prototypes/labgraph_monitor/src/mocks/util/selectMock.ts new file mode 100644 index 000000000..cc60057bd --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/mocks/util/selectMock.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import demo from '../json/demo.json'; +import simpleViz from '../json/simple_viz.json'; +import simpleVizZMQ from '../json/simple_viz_zmq.json'; +import simpleVizFixedRate from '../json/simple_viz_fixed_rate.json'; +import simulation from '../json/simulation.json'; +import MOCK from '../enums/MOCK'; + +/** + * A function that picks a mock example based on the argument passed + * + * @param {string} mock the name of the mock example + * @returns a parsed version of the JSON file containing the mock sample. + */ +export const selectMock = (mock: string) => { + switch (mock) { + case MOCK.SIMPLE_VIZ: + return simpleViz; + + case MOCK.SIMULATION: + return simulation; + + case MOCK.SIMPLE_VIZ_ZMQ: + return simpleVizZMQ; + + case MOCK.SIMPLE_VIZ_FIXED_RATE: + return simpleVizFixedRate; + + default: + return demo; + } +}; diff --git a/extensions/prototypes/labgraph_monitor/src/pages/Home/Home.tsx b/extensions/prototypes/labgraph_monitor/src/pages/Home/Home.tsx new file mode 100644 index 000000000..7a9d2ce85 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/pages/Home/Home.tsx @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import React from 'react'; +import { SettingPanel, Graph } from '../../components'; +import { WSContextProvider } from '../../contexts'; + +/** + * A component that represents the Home page of the application + * + * @returns {JSX.Element} + */ +const Home: React.FC = (): JSX.Element => { + return ( + + + + + + + ); +}; + +export default Home; diff --git a/extensions/prototypes/labgraph_monitor/src/pages/Home/__test__/Home.test.tsx b/extensions/prototypes/labgraph_monitor/src/pages/Home/__test__/Home.test.tsx new file mode 100644 index 000000000..3057f9f8a --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/pages/Home/__test__/Home.test.tsx @@ -0,0 +1,28 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { render, screen } from '@testing-library/react'; +import { store } from '../../../redux/store'; +import { Provider } from 'react-redux'; +import Home from '../Home'; + +const MockHome = () => { + return ( + + + + ); +}; + +describe('Home', () => { + it('should render Home component', async () => { + render(); + const settingPanel = screen.getByTestId('setting-panel'); + const graph = screen.getByTestId('graph'); + expect(settingPanel).toBeInTheDocument(); + expect(graph).toBeInTheDocument(); + }); +}); diff --git a/extensions/prototypes/labgraph_monitor/src/pages/index.ts b/extensions/prototypes/labgraph_monitor/src/pages/index.ts new file mode 100644 index 000000000..1d9354179 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/pages/index.ts @@ -0,0 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +export { default as Home } from './Home/Home'; diff --git a/extensions/prototypes/labgraph_monitor/src/react-app-env.d.ts b/extensions/prototypes/labgraph_monitor/src/react-app-env.d.ts new file mode 100644 index 000000000..11724e4aa --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/react-app-env.d.ts @@ -0,0 +1,7 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/// diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/config/configReducer.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/config/configReducer.ts new file mode 100644 index 000000000..888d9e01d --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/config/configReducer.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { Edge, Node } from 'react-flow-renderer'; +import IConfig from './interfaces/IConfig'; + +const initialState: IConfig = { + panelOpen: false, + tabIndex: '1', + selectedNode: {} as Node, + selectedEdge: {} as Edge, +}; + +export const configSlice = createSlice({ + name: 'config', + initialState, + reducers: { + setPanel: (state, action: PayloadAction) => { + state.panelOpen = action.payload; + }, + setTabIndex: (state, action: PayloadAction) => { + state.tabIndex = action.payload; + }, + + setSelectedNode: (state, action: PayloadAction) => { + state.selectedNode = action.payload; + }, + + setSelectedEdge: (state, action: PayloadAction) => { + state.selectedEdge = action.payload; + }, + }, +}); + +export const { setPanel, setTabIndex, setSelectedNode, setSelectedEdge } = + configSlice.actions; + +export default configSlice.reducer; diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/config/interfaces/IConfig.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/config/interfaces/IConfig.ts new file mode 100644 index 000000000..90a5eeecb --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/config/interfaces/IConfig.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { Edge, Node } from 'react-flow-renderer'; + +interface IConfig { + panelOpen: boolean; + tabIndex: string; + selectedNode: Node; + selectedEdge: Edge; +} + +export default IConfig; diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/common/interfaces/IGraph.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/common/interfaces/IGraph.ts new file mode 100644 index 000000000..3b53d7c7b --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/common/interfaces/IGraph.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import INode from './INode'; + +interface IGraph { + name: string; + nodes: INode; +} + +export default IGraph; diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/common/interfaces/INode.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/common/interfaces/INode.ts new file mode 100644 index 000000000..1e4e58895 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/common/interfaces/INode.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +interface INode { + [name: string]: { + upstreams: { + [upstream: string]: Array<{ + name: string; + fields: { + [fieldName: string]: string; + }; + }>; + }; + }; +} + +export default INode; diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/interfaces/IMock.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/interfaces/IMock.ts new file mode 100644 index 000000000..6d6df85e9 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/interfaces/IMock.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import IGraph from '../../common/interfaces/IGraph'; + +interface IMock { + mockGraph: IGraph; +} + +export default IMock; diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/mockReducer.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/mockReducer.ts new file mode 100644 index 000000000..ffe996def --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/mock/mockReducer.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import IGraph from '../common/interfaces/IGraph'; +import IMock from './interfaces/IMock'; +import { selectMock } from '../../../../mocks'; + +const initialState: IMock = { + mockGraph: {} as IGraph, +}; + +export const mockSlice = createSlice({ + name: 'mock', + initialState, + reducers: { + setMockGraph: (state, action: PayloadAction) => { + const { + stream_batch: { + 'labgraph.monitor': { samples }, + }, + } = selectMock(action.payload); + + state.mockGraph = samples[0]['data']; + }, + + copyRealtimeGraph: (state, action: PayloadAction) => { + state.mockGraph = action.payload; + }, + }, +}); + +export const { copyRealtimeGraph, setMockGraph } = mockSlice.actions; + +export default mockSlice.reducer; diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/ws/WSReducer.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/ws/WSReducer.ts new file mode 100644 index 000000000..de188ad47 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/ws/WSReducer.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import IWS from './interfaces/IWS'; +import IGraph from '../common/interfaces/IGraph'; +import WS_STATE from './enums/WS_STATE'; + +const initialState: IWS = { + connection: WS_STATE.DISCONNECTED, + graph: {} as IGraph, +}; + +const WSSlice = createSlice({ + name: 'ws', + initialState, + reducers: { + setConnection: (state, action: PayloadAction) => { + state.connection = action.payload; + }, + + setGraph: (state, action: PayloadAction) => { + state.graph = action.payload; + }, + }, +}); + +export const { setConnection, setGraph } = WSSlice.actions; + +export default WSSlice.reducer; diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/ws/enums/WS_STATE.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/ws/enums/WS_STATE.ts new file mode 100644 index 000000000..39d712ce0 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/ws/enums/WS_STATE.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +enum WS_STATE { + CONNECTED = 'CONNECTED', + IS_CONNECTING = 'IS_CONNECTING', + DISCONNECTED = 'DISCONNECTED', + IS_DISCONNECTING = 'IS_DISCONNECTING', +} + +export default WS_STATE; diff --git a/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/ws/interfaces/IWS.ts b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/ws/interfaces/IWS.ts new file mode 100644 index 000000000..590496de2 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/redux/reducers/graph/ws/interfaces/IWS.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import IGraph from '../../common/interfaces/IGraph'; +import WS_STATE from '../enums/WS_STATE'; + +interface IWS { + connection: WS_STATE; + graph: IGraph; +} + +export default IWS; diff --git a/extensions/prototypes/labgraph_monitor/src/redux/store.ts b/extensions/prototypes/labgraph_monitor/src/redux/store.ts new file mode 100644 index 000000000..3d9d9b920 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/redux/store.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { configureStore } from '@reduxjs/toolkit'; +import configReducer from './reducers/config/configReducer'; +import mockReducer from './reducers/graph/mock/mockReducer'; +import WSReducer from './reducers/graph/ws/WSReducer'; + +export const store = configureStore({ + reducer: { + config: configReducer, + mock: mockReducer, + ws: WSReducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: false, + }), +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/extensions/prototypes/labgraph_monitor/src/reportWebVitals.ts b/extensions/prototypes/labgraph_monitor/src/reportWebVitals.ts new file mode 100644 index 000000000..03e3f23c4 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/reportWebVitals.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { ReportHandler } from 'web-vitals'; + +const reportWebVitals = (onPerfEntry?: ReportHandler) => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then( + ({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + } + ); + } +}; + +export default reportWebVitals; diff --git a/extensions/prototypes/labgraph_monitor/src/setupTests.ts b/extensions/prototypes/labgraph_monitor/src/setupTests.ts new file mode 100644 index 000000000..018b9ca07 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/setupTests.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import '@testing-library/jest-dom'; +import '@testing-library/jest-dom/extend-expect'; +import ResizeObserver from 'resize-observer-polyfill'; + +global.ResizeObserver = ResizeObserver; diff --git a/extensions/prototypes/labgraph_monitor/src/statics/css/global.css b/extensions/prototypes/labgraph_monitor/src/statics/css/global.css new file mode 100644 index 000000000..93f43c534 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/src/statics/css/global.css @@ -0,0 +1,15 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800;900&display=swap'); + +* { + font-family: 'Poppins', sans-serif !important; +} + +.react-flow__edge { + cursor: pointer !important; +} diff --git a/extensions/prototypes/labgraph_monitor/tsconfig.json b/extensions/prototypes/labgraph_monitor/tsconfig.json new file mode 100644 index 000000000..831946b19 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es6", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/extensions/prototypes/labgraph_monitor/yarn.lock b/extensions/prototypes/labgraph_monitor/yarn.lock new file mode 100644 index 000000000..e36d0c9e8 --- /dev/null +++ b/extensions/prototypes/labgraph_monitor/yarn.lock @@ -0,0 +1,9601 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.0.tgz#72becdf17ee44b2d1ac5651fb12f1952c336fe23" + integrity sha512-d5RysTlJ7hmw5Tw4UxgxcY3lkMe92n8sXCcuLPAyIAHK6j8DefDwtGnVVDgOnv+RnEosulDJ9NPKQL27bDId0g== + dependencies: + "@jridgewell/trace-mapping" "^0.3.0" + +"@apideck/better-ajv-errors@^0.3.1": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.3.tgz#ab0b1e981e1749bf59736cf7ebe25cfc9f949c15" + integrity sha512-9o+HO2MbJhJHjDYZaDxJmSDckvDpiuItEsrIShV0DXeCshXWRHhqYyU/PKHMkuClOmFnZhRd6wzv4vpDu/dRKg== + dependencies: + json-schema "^0.4.0" + jsonpointer "^5.0.0" + leven "^3.1.0" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.4", "@babel/compat-data@^7.16.8": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.0.tgz#86850b8597ea6962089770952075dcaabb8dba34" + integrity sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng== + +"@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.17.2", "@babel/core@^7.7.2", "@babel/core@^7.8.0": + version "7.17.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.2.tgz#2c77fc430e95139d816d39b113b31bf40fb22337" + integrity sha512-R3VH5G42VSDolRHyUO4V2cfag8WHcZyxdq5Z/m8Xyb92lW/Erm/6kM+XtRFGf3Mulre3mveni2NHfEUws8wSvw== + dependencies: + "@ampproject/remapping" "^2.0.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.0" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helpers" "^7.17.2" + "@babel/parser" "^7.17.0" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.0" + "@babel/types" "^7.17.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + +"@babel/eslint-parser@^7.16.3": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.17.0.tgz#eabb24ad9f0afa80e5849f8240d0e5facc2d90d6" + integrity sha512-PUEJ7ZBXbRkbq3qqM/jZ2nIuakUBqCYc7Qf52Lj7dlZ6zERnqisdHioL0l4wwQZnmskMeasqUNzLBFKs3nylXA== + dependencies: + eslint-scope "^5.1.1" + eslint-visitor-keys "^2.1.0" + semver "^6.3.0" + +"@babel/generator@^7.17.0", "@babel/generator@^7.7.2": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.0.tgz#7bd890ba706cd86d3e2f727322346ffdbf98f65e" + integrity sha512-I3Omiv6FGOC29dtlZhkfXO6pgkmukJSlT26QjVvS1DGZe/NzSVCPG41X0tS21oZkJYlovfj9qDWgKP+Cn4bXxw== + dependencies: + "@babel/types" "^7.17.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" + integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b" + integrity sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz#06e66c5f299601e6c7da350049315e83209d551b" + integrity sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA== + dependencies: + "@babel/compat-data" "^7.16.4" + "@babel/helper-validator-option" "^7.16.7" + browserslist "^4.17.5" + semver "^6.3.0" + +"@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.1": + version "7.17.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz#9699f14a88833a7e055ce57dcd3ffdcd25186b21" + integrity sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + +"@babel/helper-create-regexp-features-plugin@^7.16.7": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz#1dcc7d40ba0c6b6b25618997c5dbfd310f186fe1" + integrity sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + regexpu-core "^5.0.1" + +"@babel/helper-define-polyfill-provider@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665" + integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA== + dependencies: + "@babel/helper-compilation-targets" "^7.13.0" + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/traverse" "^7.13.0" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-explode-assignable-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a" + integrity sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" + integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== + dependencies: + "@babel/helper-get-function-arity" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helper-get-function-arity@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" + integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-member-expression-to-functions@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz#42b9ca4b2b200123c3b7e726b0ae5153924905b0" + integrity sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-module-transforms@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz#7665faeb721a01ca5327ddc6bba15a5cb34b6a41" + integrity sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helper-optimise-call-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2" + integrity sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" + integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== + +"@babel/helper-remap-async-to-generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" + integrity sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-wrap-function" "^7.16.8" + "@babel/types" "^7.16.8" + +"@babel/helper-replace-supers@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz#e9f5f5f32ac90429c1a4bdec0f231ef0c2838ab1" + integrity sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helper-simple-access@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz#d656654b9ea08dbb9659b69d61063ccd343ff0f7" + integrity sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-skip-transparent-expression-wrappers@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09" + integrity sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/helper-validator-option@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== + +"@babel/helper-wrap-function@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz#58afda087c4cd235de92f7ceedebca2c41274200" + integrity sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw== + dependencies: + "@babel/helper-function-name" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.8" + "@babel/types" "^7.16.8" + +"@babel/helpers@^7.17.2": + version "7.17.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.2.tgz#23f0a0746c8e287773ccd27c14be428891f63417" + integrity sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ== + dependencies: + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.0" + "@babel/types" "^7.17.0" + +"@babel/highlight@^7.16.7": + version "7.16.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" + integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.0.tgz#f0ac33eddbe214e4105363bb17c3341c5ffcc43c" + integrity sha512-VKXSCQx5D8S04ej+Dqsr1CzYvvWgf20jIw2D+YhQCrIlr2UZGaDds23Y0xg75/skOxpLCRpUZvk/1EAVkGoDOw== + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" + integrity sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz#cc001234dfc139ac45f6bcf801866198c8c72ff9" + integrity sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" + "@babel/plugin-proposal-optional-chaining" "^7.16.7" + +"@babel/plugin-proposal-async-generator-functions@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz#3bdd1ebbe620804ea9416706cd67d60787504bc8" + integrity sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-remap-async-to-generator" "^7.16.8" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-proposal-class-properties@^7.16.0", "@babel/plugin-proposal-class-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz#925cad7b3b1a2fcea7e59ecc8eb5954f961f91b0" + integrity sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-proposal-class-static-block@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.7.tgz#712357570b612106ef5426d13dc433ce0f200c2a" + integrity sha512-dgqJJrcZoG/4CkMopzhPJjGxsIe9A8RlkQLnL/Vhhx8AA9ZuaRwGSlscSh42hazc7WSrya/IK7mTeoF0DP9tEw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-proposal-decorators@^7.16.4": + version "7.17.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.17.2.tgz#c36372ddfe0360cac1ee331a238310bddca11493" + integrity sha512-WH8Z95CwTq/W8rFbMqb9p3hicpt4RX4f0K659ax2VHxgOyT6qQmUaEVEjIh4WR9Eh9NymkVn5vwsrE68fAQNUw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.17.1" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/plugin-syntax-decorators" "^7.17.0" + charcodes "^0.2.0" + +"@babel/plugin-proposal-dynamic-import@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz#c19c897eaa46b27634a00fee9fb7d829158704b2" + integrity sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-proposal-export-namespace-from@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz#09de09df18445a5786a305681423ae63507a6163" + integrity sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-proposal-json-strings@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz#9732cb1d17d9a2626a08c5be25186c195b6fa6e8" + integrity sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-proposal-logical-assignment-operators@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz#be23c0ba74deec1922e639832904be0bea73cdea" + integrity sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz#141fc20b6857e59459d430c850a0011e36561d99" + integrity sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-proposal-numeric-separator@^7.16.0", "@babel/plugin-proposal-numeric-separator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz#d6b69f4af63fb38b6ca2558442a7fb191236eba9" + integrity sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-object-rest-spread@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.7.tgz#94593ef1ddf37021a25bdcb5754c4a8d534b01d8" + integrity sha512-3O0Y4+dw94HA86qSg9IHfyPktgR7q3gpNVAeiKQd+8jBKFaU5NQS1Yatgo4wY+UFNuLjvxcSmzcsHqrhgTyBUA== + dependencies: + "@babel/compat-data" "^7.16.4" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.16.7" + +"@babel/plugin-proposal-optional-catch-binding@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz#c623a430674ffc4ab732fd0a0ae7722b67cb74cf" + integrity sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@^7.16.0", "@babel/plugin-proposal-optional-chaining@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz#7cd629564724816c0e8a969535551f943c64c39a" + integrity sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-proposal-private-methods@^7.16.0", "@babel/plugin-proposal-private-methods@^7.16.11": + version "7.16.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz#e8df108288555ff259f4527dbe84813aac3a1c50" + integrity sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.16.10" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-proposal-private-property-in-object@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz#b0b8cef543c2c3d57e59e2c611994861d46a3fce" + integrity sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-proposal-unicode-property-regex@^7.16.7", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz#635d18eb10c6214210ffc5ff4932552de08188a2" + integrity sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-decorators@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.17.0.tgz#a2be3b2c9fe7d78bd4994e790896bc411e2f166d" + integrity sha512-qWe85yCXsvDEluNP0OyeQjH63DlhAR3W7K9BxxU1MvbDb48tgBG+Ao6IJJ6smPDrrVzSQZrbF6donpkFBMcs3A== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-flow@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.16.7.tgz#202b147e5892b8452bbb0bb269c7ed2539ab8832" + integrity sha512-UDo3YGQO0jH6ytzVwgSLv9i/CzMcUjbKenL67dTrAZPPv6GFAtDhe6jqnvmoKzC/7htNTohhos+onPtDMqJwaQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" + integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.16.7", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8" + integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-arrow-functions@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz#44125e653d94b98db76369de9c396dc14bef4154" + integrity sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-async-to-generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz#b83dff4b970cf41f1b819f8b49cc0cfbaa53a808" + integrity sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-remap-async-to-generator" "^7.16.8" + +"@babel/plugin-transform-block-scoped-functions@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz#4d0d57d9632ef6062cdf354bb717102ee042a620" + integrity sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-block-scoping@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz#f50664ab99ddeaee5bc681b8f3a6ea9d72ab4f87" + integrity sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-classes@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz#8f4b9562850cd973de3b498f1218796eb181ce00" + integrity sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz#66dee12e46f61d2aae7a73710f591eb3df616470" + integrity sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-destructuring@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.7.tgz#ca9588ae2d63978a4c29d3f33282d8603f618e23" + integrity sha512-VqAwhTHBnu5xBVDCvrvqJbtLUa++qZaWC0Fgr2mqokBlulZARGyIvZDoqbPlPaKImQ9dKAcCzbv+ul//uqu70A== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-dotall-regex@^7.16.7", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz#6b2d67686fab15fb6a7fd4bd895d5982cfc81241" + integrity sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-duplicate-keys@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz#2207e9ca8f82a0d36a5a67b6536e7ef8b08823c9" + integrity sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-exponentiation-operator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz#efa9862ef97e9e9e5f653f6ddc7b665e8536fe9b" + integrity sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-flow-strip-types@^7.16.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.16.7.tgz#291fb140c78dabbf87f2427e7c7c332b126964b8" + integrity sha512-mzmCq3cNsDpZZu9FADYYyfZJIOrSONmHcop2XEKPdBNMa4PDC4eEvcOvzZaCNcjKu72v0XQlA5y1g58aLRXdYg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-flow" "^7.16.7" + +"@babel/plugin-transform-for-of@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz#649d639d4617dff502a9a158c479b3b556728d8c" + integrity sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz#5ab34375c64d61d083d7d2f05c38d90b97ec65cf" + integrity sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA== + dependencies: + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz#254c9618c5ff749e87cb0c0cef1a0a050c0bdab1" + integrity sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-member-expression-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz#6e5dcf906ef8a098e630149d14c867dd28f92384" + integrity sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-modules-amd@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz#b28d323016a7daaae8609781d1f8c9da42b13186" + integrity sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g== + dependencies: + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-commonjs@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz#cdee19aae887b16b9d331009aa9a219af7c86afe" + integrity sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA== + dependencies: + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-simple-access" "^7.16.7" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-systemjs@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz#887cefaef88e684d29558c2b13ee0563e287c2d7" + integrity sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw== + dependencies: + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-umd@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz#23dad479fa585283dbd22215bff12719171e7618" + integrity sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ== + dependencies: + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz#7f860e0e40d844a02c9dcf9d84965e7dfd666252" + integrity sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + +"@babel/plugin-transform-new-target@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz#9967d89a5c243818e0800fdad89db22c5f514244" + integrity sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-object-super@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz#ac359cf8d32cf4354d27a46867999490b6c32a94" + integrity sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + +"@babel/plugin-transform-parameters@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz#a1721f55b99b736511cb7e0152f61f17688f331f" + integrity sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-property-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz#2dadac85155436f22c696c4827730e0fe1057a55" + integrity sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-react-constant-elements@^7.12.1": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.16.7.tgz#19e9e4c2df2f6c3e6b3aea11778297d81db8df62" + integrity sha512-lF+cfsyTgwWkcw715J88JhMYJ5GpysYNLhLP1PkvkhTRN7B3e74R/1KsDxFxhRpSn0UUD3IWM4GvdBR2PEbbQQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-react-display-name@^7.16.0", "@babel/plugin-transform-react-display-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz#7b6d40d232f4c0f550ea348593db3b21e2404340" + integrity sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-react-jsx-development@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz#43a00724a3ed2557ed3f276a01a929e6686ac7b8" + integrity sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.16.7" + +"@babel/plugin-transform-react-jsx@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.16.7.tgz#86a6a220552afd0e4e1f0388a68a372be7add0d4" + integrity sha512-8D16ye66fxiE8m890w0BpPpngG9o9OVBBy0gH2E+2AR7qMR2ZpTYJEqLxAsoroenMId0p/wMW+Blc0meDgu0Ag== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-jsx" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/plugin-transform-react-pure-annotations@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz#232bfd2f12eb551d6d7d01d13fe3f86b45eb9c67" + integrity sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-regenerator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz#9e7576dc476cb89ccc5096fff7af659243b4adeb" + integrity sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz#1d798e078f7c5958eec952059c460b220a63f586" + integrity sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-runtime@^7.16.4": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz#0a2e08b5e2b2d95c4b1d3b3371a2180617455b70" + integrity sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + babel-plugin-polyfill-corejs2 "^0.3.0" + babel-plugin-polyfill-corejs3 "^0.5.0" + babel-plugin-polyfill-regenerator "^0.3.0" + semver "^6.3.0" + +"@babel/plugin-transform-shorthand-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz#e8549ae4afcf8382f711794c0c7b6b934c5fbd2a" + integrity sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-spread@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz#a303e2122f9f12e0105daeedd0f30fb197d8ff44" + integrity sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" + +"@babel/plugin-transform-sticky-regex@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz#c84741d4f4a38072b9a1e2e3fd56d359552e8660" + integrity sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-template-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz#f3d1c45d28967c8e80f53666fc9c3e50618217ab" + integrity sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-typeof-symbol@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz#9cdbe622582c21368bd482b660ba87d5545d4f7e" + integrity sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-typescript@^7.16.7": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz#591ce9b6b83504903fa9dd3652c357c2ba7a1ee0" + integrity sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-typescript" "^7.16.7" + +"@babel/plugin-transform-unicode-escapes@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz#da8717de7b3287a2c6d659750c964f302b31ece3" + integrity sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-unicode-regex@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz#0f7aa4a501198976e25e82702574c34cfebe9ef2" + integrity sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.16.4": + version "7.16.11" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.11.tgz#5dd88fd885fae36f88fd7c8342475c9f0abe2982" + integrity sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g== + dependencies: + "@babel/compat-data" "^7.16.8" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.16.7" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.16.7" + "@babel/plugin-proposal-async-generator-functions" "^7.16.8" + "@babel/plugin-proposal-class-properties" "^7.16.7" + "@babel/plugin-proposal-class-static-block" "^7.16.7" + "@babel/plugin-proposal-dynamic-import" "^7.16.7" + "@babel/plugin-proposal-export-namespace-from" "^7.16.7" + "@babel/plugin-proposal-json-strings" "^7.16.7" + "@babel/plugin-proposal-logical-assignment-operators" "^7.16.7" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.7" + "@babel/plugin-proposal-numeric-separator" "^7.16.7" + "@babel/plugin-proposal-object-rest-spread" "^7.16.7" + "@babel/plugin-proposal-optional-catch-binding" "^7.16.7" + "@babel/plugin-proposal-optional-chaining" "^7.16.7" + "@babel/plugin-proposal-private-methods" "^7.16.11" + "@babel/plugin-proposal-private-property-in-object" "^7.16.7" + "@babel/plugin-proposal-unicode-property-regex" "^7.16.7" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.16.7" + "@babel/plugin-transform-async-to-generator" "^7.16.8" + "@babel/plugin-transform-block-scoped-functions" "^7.16.7" + "@babel/plugin-transform-block-scoping" "^7.16.7" + "@babel/plugin-transform-classes" "^7.16.7" + "@babel/plugin-transform-computed-properties" "^7.16.7" + "@babel/plugin-transform-destructuring" "^7.16.7" + "@babel/plugin-transform-dotall-regex" "^7.16.7" + "@babel/plugin-transform-duplicate-keys" "^7.16.7" + "@babel/plugin-transform-exponentiation-operator" "^7.16.7" + "@babel/plugin-transform-for-of" "^7.16.7" + "@babel/plugin-transform-function-name" "^7.16.7" + "@babel/plugin-transform-literals" "^7.16.7" + "@babel/plugin-transform-member-expression-literals" "^7.16.7" + "@babel/plugin-transform-modules-amd" "^7.16.7" + "@babel/plugin-transform-modules-commonjs" "^7.16.8" + "@babel/plugin-transform-modules-systemjs" "^7.16.7" + "@babel/plugin-transform-modules-umd" "^7.16.7" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.16.8" + "@babel/plugin-transform-new-target" "^7.16.7" + "@babel/plugin-transform-object-super" "^7.16.7" + "@babel/plugin-transform-parameters" "^7.16.7" + "@babel/plugin-transform-property-literals" "^7.16.7" + "@babel/plugin-transform-regenerator" "^7.16.7" + "@babel/plugin-transform-reserved-words" "^7.16.7" + "@babel/plugin-transform-shorthand-properties" "^7.16.7" + "@babel/plugin-transform-spread" "^7.16.7" + "@babel/plugin-transform-sticky-regex" "^7.16.7" + "@babel/plugin-transform-template-literals" "^7.16.7" + "@babel/plugin-transform-typeof-symbol" "^7.16.7" + "@babel/plugin-transform-unicode-escapes" "^7.16.7" + "@babel/plugin-transform-unicode-regex" "^7.16.7" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.16.8" + babel-plugin-polyfill-corejs2 "^0.3.0" + babel-plugin-polyfill-corejs3 "^0.5.0" + babel-plugin-polyfill-regenerator "^0.3.0" + core-js-compat "^3.20.2" + semver "^6.3.0" + +"@babel/preset-modules@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" + integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-react@^7.12.5", "@babel/preset-react@^7.16.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.16.7.tgz#4c18150491edc69c183ff818f9f2aecbe5d93852" + integrity sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-transform-react-display-name" "^7.16.7" + "@babel/plugin-transform-react-jsx" "^7.16.7" + "@babel/plugin-transform-react-jsx-development" "^7.16.7" + "@babel/plugin-transform-react-pure-annotations" "^7.16.7" + +"@babel/preset-typescript@^7.16.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz#ab114d68bb2020afc069cd51b37ff98a046a70b9" + integrity sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-transform-typescript" "^7.16.7" + +"@babel/runtime-corejs3@^7.10.2": + version "7.17.2" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.2.tgz#fdca2cd05fba63388babe85d349b6801b008fd13" + integrity sha512-NcKtr2epxfIrNM4VOmPKO46TvDMCBhgi2CrSHaEarrz+Plk2K5r9QemmOFTGpZaoKnWoGH5MO+CzeRsih/Fcgg== + dependencies: + core-js-pure "^3.20.2" + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.17.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.17.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" + integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.16.7", "@babel/template@^7.3.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.0", "@babel/traverse@^7.7.2": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.0.tgz#3143e5066796408ccc880a33ecd3184f3e75cd30" + integrity sha512-fpFIXvqD6kC7c7PUNnZ0Z8cQXlarCLtCUpt2S1Dx7PjoRtCFffvOkHHSom+m5HIxMZn5bIBVb71lhabcmjEsqg== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.0" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.0" + "@babel/types" "^7.17.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" + integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@csstools/normalize.css@*": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4" + integrity sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg== + +"@csstools/postcss-font-format-keywords@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.0.tgz#7e7df948a83a0dfb7eb150a96e2390ac642356a1" + integrity sha512-oO0cZt8do8FdVBX8INftvIA4lUrKUSCcWUf9IwH9IPWOgKT22oAZFXeHLoDK7nhB2SmkNycp5brxfNMRLIhd6Q== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-hwb-function@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.0.tgz#d6785c1c5ba8152d1d392c66f3a6a446c6034f6d" + integrity sha512-VSTd7hGjmde4rTj1rR30sokY3ONJph1reCBTUXqeW1fKwETPy1x4t/XIeaaqbMbC5Xg4SM/lyXZ2S8NELT2TaA== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-is-pseudo-class@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.0.tgz#219a1c1d84de7d9e9b7e662a57fdc194eac38ea7" + integrity sha512-WnfZlyuh/CW4oS530HBbrKq0G8BKl/bsNr5NMFoubBFzJfvFRGJhplCgIJYWUidLuL3WJ/zhMtDIyNFTqhx63Q== + dependencies: + postcss-selector-parser "^6.0.9" + +"@csstools/postcss-normalize-display-values@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.0.tgz#ce698f688c28517447aedf15a9037987e3d2dc97" + integrity sha512-bX+nx5V8XTJEmGtpWTO6kywdS725t71YSLlxWt78XoHUbELWgoCXeOFymRJmL3SU1TLlKSIi7v52EWqe60vJTQ== + dependencies: + postcss-value-parser "^4.2.0" + +"@date-io/core@^2.13.1": + version "2.13.1" + resolved "https://registry.yarnpkg.com/@date-io/core/-/core-2.13.1.tgz#f041765aff5c55fbc7e37fdd75fc1792733426d6" + integrity sha512-pVI9nfkf2qClb2Cxdq0Q4zJhdawMG4ybWZUVGifT78FDwzRMX2SwXBb55s5NRJk0HcIicDuxktmCtemZqMH1Zg== + +"@date-io/date-fns@^2.11.0": + version "2.13.1" + resolved "https://registry.yarnpkg.com/@date-io/date-fns/-/date-fns-2.13.1.tgz#19d8a245dab61c03c95ba492d679d98d2b0b4af5" + integrity sha512-8fmfwjiLMpFLD+t4NBwDx0eblWnNcgt4NgfT/uiiQTGI81fnPu9tpBMYdAcuWxaV7LLpXgzLBx1SYWAMDVUDQQ== + dependencies: + "@date-io/core" "^2.13.1" + +"@date-io/dayjs@^2.11.0": + version "2.13.1" + resolved "https://registry.yarnpkg.com/@date-io/dayjs/-/dayjs-2.13.1.tgz#98461d22ee98179b9f2dca3b36f1b618704ae593" + integrity sha512-5bL4WWWmlI4uGZVScANhHJV7Mjp93ec2gNeUHDqqLaMZhp51S0NgD25oqj/k0LqBn1cdU2MvzNpk/ObMmVv5cQ== + dependencies: + "@date-io/core" "^2.13.1" + +"@date-io/luxon@^2.11.1": + version "2.13.1" + resolved "https://registry.yarnpkg.com/@date-io/luxon/-/luxon-2.13.1.tgz#3701b3cabfffda5102af302979aa6e58acfda91a" + integrity sha512-yG+uM7lXfwLyKKEwjvP8oZ7qblpmfl9gxQYae55ifbwiTs0CoCTkYkxEaQHGkYtTqGTzLqcb0O9Pzx6vgWg+yg== + dependencies: + "@date-io/core" "^2.13.1" + +"@date-io/moment@^2.11.0": + version "2.13.1" + resolved "https://registry.yarnpkg.com/@date-io/moment/-/moment-2.13.1.tgz#122a51e4bdedf71ff3babb264427737dc022c1e6" + integrity sha512-XX1X/Tlvl3TdqQy2j0ZUtEJV6Rl8tOyc5WOS3ki52He28Uzme4Ro/JuPWTMBDH63weSWIZDlbR7zBgp3ZA2y1A== + dependencies: + "@date-io/core" "^2.13.1" + +"@emotion/babel-plugin@^11.3.0": + version "11.7.2" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.7.2.tgz#fec75f38a6ab5b304b0601c74e2a5e77c95e5fa0" + integrity sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ== + dependencies: + "@babel/helper-module-imports" "^7.12.13" + "@babel/plugin-syntax-jsx" "^7.12.13" + "@babel/runtime" "^7.13.10" + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.5" + "@emotion/serialize" "^1.0.2" + babel-plugin-macros "^2.6.1" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.0.13" + +"@emotion/cache@^11.7.1": + version "11.7.1" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.7.1.tgz#08d080e396a42e0037848214e8aa7bf879065539" + integrity sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A== + dependencies: + "@emotion/memoize" "^0.7.4" + "@emotion/sheet" "^1.1.0" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + stylis "4.0.13" + +"@emotion/hash@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" + integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== + +"@emotion/is-prop-valid@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz#cbd843d409dfaad90f9404e7c0404c55eae8c134" + integrity sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw== + dependencies: + "@emotion/memoize" "^0.7.4" + +"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50" + integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== + +"@emotion/react@^11.7.1": + version "11.7.1" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.7.1.tgz#3f800ce9b20317c13e77b8489ac4a0b922b2fe07" + integrity sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/cache" "^11.7.1" + "@emotion/serialize" "^1.0.2" + "@emotion/sheet" "^1.1.0" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965" + integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A== + dependencies: + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.4" + "@emotion/unitless" "^0.7.5" + "@emotion/utils" "^1.0.0" + csstype "^3.0.2" + +"@emotion/sheet@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.0.tgz#56d99c41f0a1cda2726a05aa6a20afd4c63e58d2" + integrity sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g== + +"@emotion/styled@^11.6.0": + version "11.6.0" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.6.0.tgz#9230d1a7bcb2ebf83c6a579f4c80e0664132d81d" + integrity sha512-mxVtVyIOTmCAkFbwIp+nCjTXJNgcz4VWkOYQro87jE2QBTydnkiYusMrRGFtzuruiGK4dDaNORk4gH049iiQuw== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/babel-plugin" "^11.3.0" + "@emotion/is-prop-valid" "^1.1.1" + "@emotion/serialize" "^1.0.2" + "@emotion/utils" "^1.0.0" + +"@emotion/unitless@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + +"@emotion/utils@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af" + integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA== + +"@emotion/weak-memoize@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" + integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== + +"@eslint/eslintrc@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" + integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.2.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@humanwhocodes/config-array@^0.9.2": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.3.tgz#f2564c744b387775b436418491f15fce6601f63e" + integrity sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" + integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + slash "^3.0.0" + +"@jest/core@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" + integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/reporters" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^27.5.1" + jest-config "^27.5.1" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-resolve-dependencies "^27.5.1" + jest-runner "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + jest-watcher "^27.5.1" + micromatch "^4.0.4" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" + integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== + dependencies: + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + +"@jest/fake-timers@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" + integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== + dependencies: + "@jest/types" "^27.5.1" + "@sinonjs/fake-timers" "^8.0.1" + "@types/node" "*" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-util "^27.5.1" + +"@jest/globals@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" + integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/types" "^27.5.1" + expect "^27.5.1" + +"@jest/reporters@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" + integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-haste-map "^27.5.1" + jest-resolve "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^8.1.0" + +"@jest/source-map@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" + integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.9" + source-map "^0.6.0" + +"@jest/test-result@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" + integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== + dependencies: + "@jest/console" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" + integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== + dependencies: + "@jest/test-result" "^27.5.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-runtime "^27.5.1" + +"@jest/transform@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" + integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^27.5.1" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-regex-util "^27.5.1" + jest-util "^27.5.1" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.4.tgz#b876e3feefb9c8d3aa84014da28b5e52a0640d72" + integrity sha512-cz8HFjOFfUBtvN+NXYSFMHYRdxZMaEl0XypVrhzxBgadKIXhIkRd8aMeHhmF56Sl7SuS8OnUpQ73/k9LE4VnLg== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.10" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.10.tgz#baf57b4e2a690d4f38560171f91783656b7f8186" + integrity sha512-Ht8wIW5v165atIX1p+JvKR5ONzUyF4Ac8DZIQ5kZs9zrb6M8SJNXpx1zn04rn65VjBMygRoMXcyYwNK0fT7bEg== + +"@jridgewell/trace-mapping@^0.3.0": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" + integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@mui/base@5.0.0-alpha.68": + version "5.0.0-alpha.68" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.68.tgz#d93d77e662bc8dce47c9415fc6cbcac6658efab7" + integrity sha512-q+3gX6EHuM/AyOn8fkoANQxSzIHBeuNsrGgb7SPP0y7NuM+4ZHG/b9882+OfHcilaSqPDWUQoLbphcBpw/m/RA== + dependencies: + "@babel/runtime" "^7.17.0" + "@emotion/is-prop-valid" "^1.1.1" + "@mui/utils" "^5.4.1" + "@popperjs/core" "^2.4.4" + clsx "^1.1.1" + prop-types "^15.7.2" + react-is "^17.0.2" + +"@mui/icons-material@^5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.4.1.tgz#20901e9a09154355b7a832180a90717938c675c4" + integrity sha512-koiq9q2GfjXRUWcC5fEi1b+EA4vfJHgIaAdBHlkOrBx2cnmmazQcyib501eodPfaZGx9BikrhivODaNQYQq8hA== + dependencies: + "@babel/runtime" "^7.17.0" + +"@mui/lab@^5.0.0-alpha.68": + version "5.0.0-alpha.68" + resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.68.tgz#a5034a8f749f3f4f0a1b8613515bed4054ade3e6" + integrity sha512-wvszkLsgXgl3kMPVpHNm9pRYld9/2r0MYRlJUEh2GWwjBPE3dDTOIF2IHgZ3WqRBnJMitzUVt7v5Lu9/grjrIQ== + dependencies: + "@babel/runtime" "^7.17.0" + "@date-io/date-fns" "^2.11.0" + "@date-io/dayjs" "^2.11.0" + "@date-io/luxon" "^2.11.1" + "@date-io/moment" "^2.11.0" + "@mui/base" "5.0.0-alpha.68" + "@mui/system" "^5.4.1" + "@mui/utils" "^5.4.1" + clsx "^1.1.1" + prop-types "^15.7.2" + react-is "^17.0.2" + react-transition-group "^4.4.2" + rifm "^0.12.1" + +"@mui/material@^5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.4.1.tgz#05d3f726771c413dc430163d7c508edfcee04807" + integrity sha512-SxAT43UAjFTBBpJrN+oGrv40xP1uCa5Z49NfHt3m93xYeFzbxKOk0V9IKU7zlUjbsaVQ0i+o24yF5GULZmynlA== + dependencies: + "@babel/runtime" "^7.17.0" + "@mui/base" "5.0.0-alpha.68" + "@mui/system" "^5.4.1" + "@mui/types" "^7.1.1" + "@mui/utils" "^5.4.1" + "@types/react-transition-group" "^4.4.4" + clsx "^1.1.1" + csstype "^3.0.10" + hoist-non-react-statics "^3.3.2" + prop-types "^15.7.2" + react-is "^17.0.2" + react-transition-group "^4.4.2" + +"@mui/private-theming@^5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.4.1.tgz#5fa6490f35e78781239f1944ae80a7006c5a7648" + integrity sha512-Xbc4MXFZxv0A3hoc4TSDBhzjhstppKfc+gQcTMqqBZQP7KjnmxF+wO7rEPQuYRBihjCqQBdrHIGMLsKWrhkZkQ== + dependencies: + "@babel/runtime" "^7.17.0" + "@mui/utils" "^5.4.1" + prop-types "^15.7.2" + +"@mui/styled-engine@^5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.4.1.tgz#1427738e71c087f7005547e17d4a59de75597850" + integrity sha512-CFLNJkopRoAuShkgUZOTBVxdTlKu4w6L4kOwPi4r3QB2XXS6O5kyLHSsg9huUbtOYk5Dv5UZyUSc5pw4J7ezdg== + dependencies: + "@babel/runtime" "^7.17.0" + "@emotion/cache" "^11.7.1" + prop-types "^15.7.2" + +"@mui/styles@^5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@mui/styles/-/styles-5.4.1.tgz#994171da902267184fffa19896ee5bbb07d4d783" + integrity sha512-ekw2NBC06re0H9SvCA1XgtFcghB8AQdGPXD3mjIz5ik+X+LvR+f2TeoCpJpkKp7UQdcNn6uuYi6BO6irTiQhdw== + dependencies: + "@babel/runtime" "^7.17.0" + "@emotion/hash" "^0.8.0" + "@mui/private-theming" "^5.4.1" + "@mui/types" "^7.1.1" + "@mui/utils" "^5.4.1" + clsx "^1.1.1" + csstype "^3.0.10" + hoist-non-react-statics "^3.3.2" + jss "^10.8.2" + jss-plugin-camel-case "^10.8.2" + jss-plugin-default-unit "^10.8.2" + jss-plugin-global "^10.8.2" + jss-plugin-nested "^10.8.2" + jss-plugin-props-sort "^10.8.2" + jss-plugin-rule-value-function "^10.8.2" + jss-plugin-vendor-prefixer "^10.8.2" + prop-types "^15.7.2" + +"@mui/system@^5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.4.1.tgz#cf253369fbf1d960c792f0ec068fa28af81be3d4" + integrity sha512-07JBYf9iQdxIHZU8cFOLoxBnkQDUPLb7UBhNxo4998yEqpWFJ00WKgEVYBKvPl0X+MRU/20wqFz6yGIuCx4AeA== + dependencies: + "@babel/runtime" "^7.17.0" + "@mui/private-theming" "^5.4.1" + "@mui/styled-engine" "^5.4.1" + "@mui/types" "^7.1.1" + "@mui/utils" "^5.4.1" + clsx "^1.1.1" + csstype "^3.0.10" + prop-types "^15.7.2" + +"@mui/types@^7.1.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.1.tgz#9cf159dc60a101ee336e6ec74193a4f5f97f6160" + integrity sha512-33hbHFLCwenTpS+T4m4Cz7cQ/ng5g+IgtINkw1uDBVvi1oM83VNt/IGzWIQNPK8H2pr0WIfkmboD501bVdYsPw== + +"@mui/utils@^5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.4.1.tgz#feb365ce9a4426587510f0943fd6d6e1889e06e6" + integrity sha512-5HzM+ZjlQqbSp7UTOvLlhAjkWB+o9Z4NzO0W+yhZ1KnxITr+zr/MBzYmmQ3kyvhui8pyhgRDoTcVgwb+02ZUZA== + dependencies: + "@babel/runtime" "^7.17.0" + "@types/prop-types" "^15.7.4" + "@types/react-is" "^16.7.1 || ^17.0.0" + prop-types "^15.7.2" + react-is "^17.0.2" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pmmmwh/react-refresh-webpack-plugin@^0.5.3": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.4.tgz#df0d0d855fc527db48aac93c218a0bf4ada41f99" + integrity sha512-zZbZeHQDnoTlt2AF+diQT0wsSXpvWiaIOZwBRdltNFhG1+I3ozyaw7U/nBiUwyJ0D+zwdXp0E3bWOl38Ag2BMw== + dependencies: + ansi-html-community "^0.0.8" + common-path-prefix "^3.0.0" + core-js-pure "^3.8.1" + error-stack-parser "^2.0.6" + find-up "^5.0.0" + html-entities "^2.1.0" + loader-utils "^2.0.0" + schema-utils "^3.0.0" + source-map "^0.7.3" + +"@popperjs/core@^2.4.4": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9" + integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA== + +"@reduxjs/toolkit@^1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.7.2.tgz#b428aaef92582379464f9de698dbb71957eafb02" + integrity sha512-wwr3//Ar8ZhM9bS58O+HCIaMlR4Y6SNHfuszz9hKnQuFIKvwaL3Kmjo6fpDKUOjo4Lv54Yi299ed8rofCJ/Vjw== + dependencies: + immer "^9.0.7" + redux "^4.1.2" + redux-thunk "^2.4.1" + reselect "^4.1.5" + +"@rollup/plugin-babel@^5.2.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879" + integrity sha512-9uIC8HZOnVLrLHxayq/PTzw+uS25E14KPUBh5ktF+18Mjo5yK0ToMMx6epY0uEgkjwJw0aBW4x2horYXh8juWw== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@rollup/pluginutils" "^3.1.0" + +"@rollup/plugin-node-resolve@^11.2.1": + version "11.2.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz#82aa59397a29cd4e13248b106e6a4a1880362a60" + integrity sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg== + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + builtin-modules "^3.1.0" + deepmerge "^4.2.2" + is-module "^1.0.0" + resolve "^1.19.0" + +"@rollup/plugin-replace@^2.4.1": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz#a2d539314fbc77c244858faa523012825068510a" + integrity sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg== + dependencies: + "@rollup/pluginutils" "^3.1.0" + magic-string "^0.25.7" + +"@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + +"@rushstack/eslint-patch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323" + integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A== + +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@socket.io/base64-arraybuffer@~1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#568d9beae00b0d835f4f8c53fd55714986492e61" + integrity sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ== + +"@socket.io/component-emitter@~3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz#8863915676f837d9dad7b76f50cb500c1e9422e9" + integrity sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q== + +"@surma/rollup-plugin-off-main-thread@^2.2.3": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053" + integrity sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ== + dependencies: + ejs "^3.1.6" + json5 "^2.2.0" + magic-string "^0.25.0" + string.prototype.matchall "^4.0.6" + +"@svgr/babel-plugin-add-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" + integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== + +"@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz#6b2c770c95c874654fd5e1d5ef475b78a0a962ef" + integrity sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg== + +"@svgr/babel-plugin-remove-jsx-empty-expression@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz#25621a8915ed7ad70da6cea3d0a6dbc2ea933efd" + integrity sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz#0b221fc57f9fcd10e91fe219e2cd0dd03145a897" + integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== + +"@svgr/babel-plugin-svg-dynamic-title@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz#139b546dd0c3186b6e5db4fefc26cb0baea729d7" + integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== + +"@svgr/babel-plugin-svg-em-dimensions@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz#6543f69526632a133ce5cabab965deeaea2234a0" + integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== + +"@svgr/babel-plugin-transform-react-native-svg@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz#00bf9a7a73f1cad3948cdab1f8dfb774750f8c80" + integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== + +"@svgr/babel-plugin-transform-svg-component@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz#583a5e2a193e214da2f3afeb0b9e8d3250126b4a" + integrity sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ== + +"@svgr/babel-preset@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-5.5.0.tgz#8af54f3e0a8add7b1e2b0fcd5a882c55393df327" + integrity sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "^5.0.1" + "@svgr/babel-plugin-replace-jsx-attribute-value" "^5.0.1" + "@svgr/babel-plugin-svg-dynamic-title" "^5.4.0" + "@svgr/babel-plugin-svg-em-dimensions" "^5.4.0" + "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" + "@svgr/babel-plugin-transform-svg-component" "^5.5.0" + +"@svgr/core@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-5.5.0.tgz#82e826b8715d71083120fe8f2492ec7d7874a579" + integrity sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ== + dependencies: + "@svgr/plugin-jsx" "^5.5.0" + camelcase "^6.2.0" + cosmiconfig "^7.0.0" + +"@svgr/hast-util-to-babel-ast@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz#5ee52a9c2533f73e63f8f22b779f93cd432a5461" + integrity sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ== + dependencies: + "@babel/types" "^7.12.6" + +"@svgr/plugin-jsx@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz#1aa8cd798a1db7173ac043466d7b52236b369000" + integrity sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA== + dependencies: + "@babel/core" "^7.12.3" + "@svgr/babel-preset" "^5.5.0" + "@svgr/hast-util-to-babel-ast" "^5.5.0" + svg-parser "^2.0.2" + +"@svgr/plugin-svgo@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz#02da55d85320549324e201c7b2e53bf431fcc246" + integrity sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ== + dependencies: + cosmiconfig "^7.0.0" + deepmerge "^4.2.2" + svgo "^1.2.2" + +"@svgr/webpack@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-5.5.0.tgz#aae858ee579f5fa8ce6c3166ef56c6a1b381b640" + integrity sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g== + dependencies: + "@babel/core" "^7.12.3" + "@babel/plugin-transform-react-constant-elements" "^7.12.1" + "@babel/preset-env" "^7.12.1" + "@babel/preset-react" "^7.12.5" + "@svgr/core" "^5.5.0" + "@svgr/plugin-jsx" "^5.5.0" + "@svgr/plugin-svgo" "^5.5.0" + loader-utils "^2.0.0" + +"@testing-library/dom@^8.0.0": + version "8.11.3" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.11.3.tgz#38fd63cbfe14557021e88982d931e33fb7c1a808" + integrity sha512-9LId28I+lx70wUiZjLvi1DB/WT2zGOxUh46glrSNMaWVx849kKAluezVzZrXJfTKKoQTmEOutLes/bHg4Bj3aA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^5.0.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.4.4" + pretty-format "^27.0.2" + +"@testing-library/jest-dom@^5.14.1": + version "5.16.2" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.2.tgz#f329b36b44aa6149cd6ced9adf567f8b6aa1c959" + integrity sha512-6ewxs1MXWwsBFZXIk4nKKskWANelkdUehchEOokHsN8X7c2eKXGw+77aRV63UU8f/DTSVUPLaGxdrj4lN7D/ug== + dependencies: + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^5.0.0" + chalk "^3.0.0" + css "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.5.6" + lodash "^4.17.15" + redent "^3.0.0" + +"@testing-library/react@^12.1.3": + version "12.1.3" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.3.tgz#ef26c5f122661ea9b6f672b23dc6b328cadbbf26" + integrity sha512-oCULRXWRrBtC9m6G/WohPo1GLcLesH7T4fuKzRAKn1CWVu9BzXtqLXDDTA6KhFNNtRwLtfSMr20HFl+Qrdrvmg== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^8.0.0" + "@types/react-dom" "*" + +"@testing-library/user-event@^13.2.1": + version "13.5.0" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" + integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg== + dependencies: + "@babel/runtime" "^7.12.5" + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@types/aria-query@^4.2.0": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" + integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== + +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": + version "7.1.18" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.18.tgz#1a29abcc411a9c05e2094c98f9a1b7da6cdf49f8" + integrity sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" + integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== + dependencies: + "@babel/types" "^7.3.0" + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.9": + version "3.5.10" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" + integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" + integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/dagre@^0.7.47": + version "0.7.47" + resolved "https://registry.yarnpkg.com/@types/dagre/-/dagre-0.7.47.tgz#1d1b89e1fac36aaf5ef5ed6274bb123073095ac5" + integrity sha512-oX+3aRf7L6Cqq1MvbWmmD7FpAU/T8URwFFuHBagAiyHILn3i+RNZ35/tvyq28de+lZGY3W19BxJ7FeITQDO7aA== + +"@types/eslint-scope@^3.7.0": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" + integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.4.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.1.tgz#c48251553e8759db9e656de3efc846954ac32304" + integrity sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/eslint@^7.28.2": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" + integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + +"@types/estree@^0.0.50": + version "0.0.50" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" + integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": + version "4.17.28" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" + integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/graceful-fs@^4.1.2": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/html-minifier-terser@^6.0.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" + integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== + +"@types/http-proxy@^1.17.8": + version "1.17.8" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.8.tgz#968c66903e7e42b483608030ee85800f22d03f55" + integrity sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@*", "@types/jest@^27.4.0": + version "27.4.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.0.tgz#037ab8b872067cae842a320841693080f9cb84ed" + integrity sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ== + dependencies: + jest-diff "^27.0.0" + pretty-format "^27.0.0" + +"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/lodash@^4.14.178": + version "4.14.178" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8" + integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + +"@types/node@*": + version "17.0.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.17.tgz#a8ddf6e0c2341718d74ee3dc413a13a042c45a0c" + integrity sha512-e8PUNQy1HgJGV3iU/Bp2+D/DXh3PYeyli8LgIwsQcs1Ar1LoaWHSIT6Rw+H2rNJmiq6SNWiDytfx8+gYj7wDHw== + +"@types/node@^16.7.13": + version "16.11.23" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.23.tgz#47677957c66c95f0d85787f4989ee89c58312480" + integrity sha512-Br/QS/NWL1/WJhCgRh7L4dJb7RAf0SHygJ+jCNdXCUSA+85v5tRz8PCdiJ9I6mP9jTZ7a1gWDeMBCUKlUZQh6g== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/prettier@^2.1.5": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17" + integrity sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA== + +"@types/prop-types@*", "@types/prop-types@^15.7.4": + version "15.7.4" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" + integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== + +"@types/q@^1.5.1": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" + integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ== + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/react-dom@*", "@types/react-dom@^17.0.9": + version "17.0.11" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466" + integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q== + dependencies: + "@types/react" "*" + +"@types/react-is@^16.7.1 || ^17.0.0": + version "17.0.3" + resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a" + integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw== + dependencies: + "@types/react" "*" + +"@types/react-redux@^7.1.20": + version "7.1.22" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.22.tgz#0eab76a37ef477cc4b53665aeaf29cb60631b72a" + integrity sha512-GxIA1kM7ClU73I6wg9IRTVwSO9GS+SAKZKe0Enj+82HMU6aoESFU2HNAdNi3+J53IaOHPiUfT3kSG4L828joDQ== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + +"@types/react-transition-group@^4.4.4": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e" + integrity sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^17.0.20": + version "17.0.39" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.39.tgz#d0f4cde092502a6db00a1cded6e6bf2abb7633ce" + integrity sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/resolve@1.17.1": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" + integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== + dependencies: + "@types/node" "*" + +"@types/retry@^0.12.0": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" + integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +"@types/serve-index@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" + integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + dependencies: + "@types/express" "*" + +"@types/serve-static@*": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/socket.io-client@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-3.0.0.tgz#d0b8ea22121b7c1df68b6a923002f9c8e3cefb42" + integrity sha512-s+IPvFoEIjKA3RdJz/Z2dGR4gLgysKi8owcnrVwNjgvc01Lk68LJDDsG2GRqegFITcxmvCMYM7bhMpwEMlHmDg== + dependencies: + socket.io-client "*" + +"@types/sockjs@^0.3.33": + version "0.3.33" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" + integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== + dependencies: + "@types/node" "*" + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/testing-library__jest-dom@^5.9.1": + version "5.14.2" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.2.tgz#564fb2b2dc827147e937a75b639a05d17ce18b44" + integrity sha512-vehbtyHUShPxIa9SioxDwCvgxukDMH//icJG90sXQBUm5lJOHLT5kNeU9tnivhnA/TkOFMzGIXN2cTc4hY8/kg== + dependencies: + "@types/jest" "*" + +"@types/trusted-types@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" + integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== + +"@types/websocket@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.5.tgz#3fb80ed8e07f88e51961211cd3682a3a4a81569c" + integrity sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ== + dependencies: + "@types/node" "*" + +"@types/ws@^8.2.2": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" + integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" + integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== + +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.11.0", "@typescript-eslint/eslint-plugin@^5.5.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.11.0.tgz#3b866371d8d75c70f9b81535e7f7d3aa26527c7a" + integrity sha512-HJh33bgzXe6jGRocOj4FmefD7hRY4itgjzOrSs3JPrTNXsX7j5+nQPciAUj/1nZtwo2kAc3C75jZO+T23gzSGw== + dependencies: + "@typescript-eslint/scope-manager" "5.11.0" + "@typescript-eslint/type-utils" "5.11.0" + "@typescript-eslint/utils" "5.11.0" + debug "^4.3.2" + functional-red-black-tree "^1.0.1" + ignore "^5.1.8" + regexpp "^3.2.0" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/experimental-utils@^5.0.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.11.0.tgz#e7b2bfd57ddda47c3f658faad57655ed9e01fea0" + integrity sha512-EPvC/bU2n1LKtzKWP1AjGWkp7r8tJ8giVlZHIODo6q7SAd6J+/9vjtEKHK2G/Qp+D2IGPsQge+oadDR3CZcFtQ== + dependencies: + "@typescript-eslint/utils" "5.11.0" + +"@typescript-eslint/parser@^5.11.0", "@typescript-eslint/parser@^5.5.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.11.0.tgz#b4fcaf65513f9b34bdcbffdda055724a5efb7e04" + integrity sha512-x0DCjetHZYBRovJdr3U0zG9OOdNXUaFLJ82ehr1AlkArljJuwEsgnud+Q7umlGDFLFrs8tU8ybQDFocp/eX8mQ== + dependencies: + "@typescript-eslint/scope-manager" "5.11.0" + "@typescript-eslint/types" "5.11.0" + "@typescript-eslint/typescript-estree" "5.11.0" + debug "^4.3.2" + +"@typescript-eslint/scope-manager@5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.11.0.tgz#f5aef83ff253f457ecbee5f46f762298f0101e4b" + integrity sha512-z+K4LlahDFVMww20t/0zcA7gq/NgOawaLuxgqGRVKS0PiZlCTIUtX0EJbC0BK1JtR4CelmkPK67zuCgpdlF4EA== + dependencies: + "@typescript-eslint/types" "5.11.0" + "@typescript-eslint/visitor-keys" "5.11.0" + +"@typescript-eslint/type-utils@5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.11.0.tgz#58be0ba73d1f6ef8983d79f7f0bc2209b253fefe" + integrity sha512-wDqdsYO6ofLaD4DsGZ0jGwxp4HrzD2YKulpEZXmgN3xo4BHJwf7kq49JTRpV0Gx6bxkSUmc9s0EIK1xPbFFpIA== + dependencies: + "@typescript-eslint/utils" "5.11.0" + debug "^4.3.2" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.11.0.tgz#ba345818a2540fdf2755c804dc2158517ab61188" + integrity sha512-cxgBFGSRCoBEhvSVLkKw39+kMzUKHlJGVwwMbPcTZX3qEhuXhrjwaZXWMxVfxDgyMm+b5Q5b29Llo2yow8Y7xQ== + +"@typescript-eslint/typescript-estree@5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.11.0.tgz#53f9e09b88368191e52020af77c312a4777ffa43" + integrity sha512-yVH9hKIv3ZN3lw8m/Jy5I4oXO4ZBMqijcXCdA4mY8ull6TPTAoQnKKrcZ0HDXg7Bsl0Unwwx7jcXMuNZc0m4lg== + dependencies: + "@typescript-eslint/types" "5.11.0" + "@typescript-eslint/visitor-keys" "5.11.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.11.0", "@typescript-eslint/utils@^5.10.2": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.11.0.tgz#d91548ef180d74c95d417950336d9260fdbe1dc5" + integrity sha512-g2I480tFE1iYRDyMhxPAtLQ9HAn0jjBtipgTCZmd9I9s11OV8CTsG+YfFciuNDcHqm4csbAgC2aVZCHzLxMSUw== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.11.0" + "@typescript-eslint/types" "5.11.0" + "@typescript-eslint/typescript-estree" "5.11.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.11.0": + version "5.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.11.0.tgz#888542381f1a2ac745b06d110c83c0b261487ebb" + integrity sha512-E8w/vJReMGuloGxJDkpPlGwhxocxOpSVgSvjiLO5IxZPmxZF30weOeJYyPSEACwM+X4NziYS9q+WkN/2DHYQwA== + dependencies: + "@typescript-eslint/types" "5.11.0" + eslint-visitor-keys "^3.0.0" + +"@webassemblyjs/ast@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" + integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + +"@webassemblyjs/floating-point-hex-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" + integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== + +"@webassemblyjs/helper-api-error@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" + integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== + +"@webassemblyjs/helper-buffer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" + integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== + +"@webassemblyjs/helper-numbers@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" + integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" + integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== + +"@webassemblyjs/helper-wasm-section@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" + integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== + 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" + +"@webassemblyjs/ieee754@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" + integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" + integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" + integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== + +"@webassemblyjs/wasm-edit@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" + integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== + 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" + +"@webassemblyjs/wasm-gen@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" + integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== + 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" + +"@webassemblyjs/wasm-opt@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" + integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== + dependencies: + "@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@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" + integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== + 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" + +"@webassemblyjs/wast-printer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" + integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +abab@^2.0.3, abab@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" + integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-import-assertions@^1.7.6: + version "1.8.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" + integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== + +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^7.0.0, acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^7.0.0, acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.2.4, acorn@^8.4.1, acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + +address@^1.0.1, address@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" + integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== + +adjust-sourcemap-loader@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz#fc4a0fd080f7d10471f30a7320f25560ade28c99" + integrity sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A== + dependencies: + loader-utils "^2.0.0" + regex-parser "^2.2.11" + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + 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" + +ajv@^8.0.0, ajv@^8.6.0, ajv@^8.8.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.10.0.tgz#e573f719bd3af069017e3b66538ab968d040e54d" + integrity sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb" + integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== + dependencies: + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" + +aria-query@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" + integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +array-flatten@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +array-includes@^3.1.3, array-includes@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" + integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.flat@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" + integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + +array.prototype.flatmap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz#908dc82d8a406930fdf38598d51e7411d18d4446" + integrity sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.19.0" + +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +ast-types-flow@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= + +async@0.9.x: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= + +async@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autoprefixer@^10.4.2: + version "10.4.2" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b" + integrity sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ== + dependencies: + browserslist "^4.19.1" + caniuse-lite "^1.0.30001297" + fraction.js "^4.1.2" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +axe-core@^4.3.5: + version "4.4.1" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413" + integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw== + +axobject-query@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" + integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== + +babel-jest@^27.4.2, babel-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" + integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== + dependencies: + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-loader@^8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.3.tgz#8986b40f1a64cacfcb4b8429320085ef68b1342d" + integrity sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw== + dependencies: + find-cache-dir "^3.3.1" + loader-utils "^1.4.0" + make-dir "^3.1.0" + schema-utils "^2.6.5" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" + integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-macros@^2.6.1: + version "2.8.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" + integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== + dependencies: + "@babel/runtime" "^7.7.2" + cosmiconfig "^6.0.0" + resolve "^1.12.0" + +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + +babel-plugin-named-asset-import@^0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz#6b7fa43c59229685368683c28bc9734f24524cc2" + integrity sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q== + +babel-plugin-polyfill-corejs2@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5" + integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w== + dependencies: + "@babel/compat-data" "^7.13.11" + "@babel/helper-define-polyfill-provider" "^0.3.1" + semver "^6.1.1" + +babel-plugin-polyfill-corejs3@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72" + integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.1" + core-js-compat "^3.21.0" + +babel-plugin-polyfill-regenerator@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" + integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.1" + +babel-plugin-transform-react-remove-prop-types@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a" + integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA== + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" + integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== + dependencies: + babel-plugin-jest-hoist "^27.5.1" + babel-preset-current-node-syntax "^1.0.0" + +babel-preset-react-app@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz#ed6005a20a24f2c88521809fa9aea99903751584" + integrity sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg== + dependencies: + "@babel/core" "^7.16.0" + "@babel/plugin-proposal-class-properties" "^7.16.0" + "@babel/plugin-proposal-decorators" "^7.16.4" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.0" + "@babel/plugin-proposal-numeric-separator" "^7.16.0" + "@babel/plugin-proposal-optional-chaining" "^7.16.0" + "@babel/plugin-proposal-private-methods" "^7.16.0" + "@babel/plugin-transform-flow-strip-types" "^7.16.0" + "@babel/plugin-transform-react-display-name" "^7.16.0" + "@babel/plugin-transform-runtime" "^7.16.4" + "@babel/preset-env" "^7.16.4" + "@babel/preset-react" "^7.16.0" + "@babel/preset-typescript" "^7.16.0" + "@babel/runtime" "^7.16.3" + babel-plugin-macros "^3.1.0" + babel-plugin-transform-react-remove-prop-types "^0.4.24" + +backo2@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= + +bfj@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2" + integrity sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw== + dependencies: + bluebird "^3.5.5" + check-types "^11.1.1" + hoopy "^0.1.4" + tryer "^1.0.1" + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +body-parser@1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" + integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA== + dependencies: + bytes "3.1.1" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.8.1" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.9.6" + raw-body "2.4.2" + type-is "~1.6.18" + +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.1, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.18.1, browserslist@^4.19.1: + version "4.19.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" + integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== + dependencies: + caniuse-lite "^1.0.30001286" + electron-to-chromium "^1.4.17" + escalade "^3.1.1" + node-releases "^2.0.1" + picocolors "^1.0.0" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== + +bufferutil@^4.0.1: + version "4.0.6" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.6.tgz#ebd6c67c7922a0e902f053e5d8be5ec850e48433" + integrity sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw== + dependencies: + node-gyp-build "^4.3.0" + +builtin-modules@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" + integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +bytes@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" + integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0, camelcase@^6.2.1: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297: + version "1.0.30001311" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001311.tgz#682ef3f4e617f1a177ad943de59775ed3032e511" + integrity sha512-mleTFtFKfykEeW34EyfhGIFjGCqzhh38Y0LhdQ9aWF+HorZTtdgKV/1hEE0NlFkG2ubvisPV6l400tlbPys98A== + +case-sensitive-paths-webpack-plugin@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" + integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== + +chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +char-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.0.tgz#16f98f3f874edceddd300fda5d58df380a7641a6" + integrity sha512-oGu2QekBMXgyQNWPDRQ001bjvDnZe4/zBTz37TMbiKz1NbNiyiH5hRkobe7npRN6GfbGbxMYFck/vQ1r9c1VMA== + +charcodes@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/charcodes/-/charcodes-0.2.0.tgz#5208d327e6cc05f99eb80ffc814707572d1f14e4" + integrity sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ== + +check-types@^11.1.1: + version "11.1.2" + resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f" + integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ== + +chokidar@^3.4.2, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +ci-info@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + +classcat@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/classcat/-/classcat-5.0.3.tgz#38eaa0ec6eb1b10faf101bbcef2afb319c23c17b" + integrity sha512-6dK2ke4VEJZOFx2ZfdDAl5OhEL8lvkl6EHF92IfRePfHxQTqir5NlcNVUv+2idjDqCX2NDc8m8YSAI5NI975ZQ== + +clean-css@^5.2.2: + version "5.2.4" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.4.tgz#982b058f8581adb2ae062520808fb2429bd487a4" + integrity sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg== + dependencies: + source-map "~0.6.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clsx@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" + integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + q "^1.1.2" + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.1.4, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colord@^2.9.1: + version "2.9.2" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1" + integrity sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ== + +colorette@^2.0.10: + version "2.0.16" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" + integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +common-path-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" + integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== + +common-tags@^1.8.0: + version "1.8.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +confusing-browser-globals@^1.0.10, confusing-browser-globals@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" + integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== + +connect-history-api-fallback@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + +core-js-compat@^3.20.2, core-js-compat@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.21.0.tgz#bcc86aa5a589cee358e7a7fa0a4979d5a76c3885" + integrity sha512-OSXseNPSK2OPJa6GdtkMz/XxeXx8/CJvfhQWTqd6neuUraujcL4jVsjkLQz1OWnax8xVQJnRPe0V2jqNWORA+A== + dependencies: + browserslist "^4.19.1" + semver "7.0.0" + +core-js-pure@^3.20.2, core-js-pure@^3.8.1: + version "3.21.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.0.tgz#819adc8dfb808205ce25b51d50591becd615db7e" + integrity sha512-VaJUunCZLnxuDbo1rNOzwbet9E1K9joiXS5+DQMPtgxd24wfsZbJZMMfQLGYMlCUvSxLfsRUUhoOR2x28mFfeg== + +core-js@^3.19.2: + version "3.21.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.0.tgz#f479dbfc3dffb035a0827602dd056839a774aa71" + integrity sha512-YUdI3fFu4TF/2WykQ2xzSiTQdldLB4KVuL9WeAy5XONZYt5Cun/fpQvctoKbCgvPhmzADeesTk/j2Rdx77AcKQ== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + +cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +css-blank-pseudo@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561" + integrity sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ== + dependencies: + postcss-selector-parser "^6.0.9" + +css-declaration-sorter@^6.0.3: + version "6.1.4" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.4.tgz#b9bfb4ed9a41f8dcca9bf7184d849ea94a8294b4" + integrity sha512-lpfkqS0fctcmZotJGhnxkIyJWvBXgpyi2wsFd4J8VB7wzyrT6Ch/3Q+FMNJpjK4gu1+GN5khOnpU2ZVKrLbhCw== + dependencies: + timsort "^0.3.0" + +css-has-pseudo@^3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz#57f6be91ca242d5c9020ee3e51bbb5b89fc7af73" + integrity sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw== + dependencies: + postcss-selector-parser "^6.0.9" + +css-loader@^6.5.1: + version "6.6.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.6.0.tgz#c792ad5510bd1712618b49381bd0310574fafbd3" + integrity sha512-FK7H2lisOixPT406s5gZM1S3l8GrfhEBT3ZiL2UX1Ng1XWs0y2GPllz/OTyvbaHe12VgQrIXIzuEGVlbUhodqg== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.5" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.3.5" + +css-minimizer-webpack-plugin@^3.2.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz#ab78f781ced9181992fe7b6e4f3422e76429878f" + integrity sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q== + dependencies: + cssnano "^5.0.6" + jest-worker "^27.0.2" + postcss "^8.3.5" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + +css-prefers-color-scheme@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz#ca8a22e5992c10a5b9d315155e7caee625903349" + integrity sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA== + +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + +css-select@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + +css-select@^4.1.3: + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" + integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== + dependencies: + boolbase "^1.0.0" + css-what "^5.1.0" + domhandler "^4.3.0" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-vendor@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d" + integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ== + dependencies: + "@babel/runtime" "^7.8.3" + is-in-browser "^1.0.2" + +css-what@^3.2.1: + version "3.4.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" + integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== + +css-what@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== + +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= + +css@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" + integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== + dependencies: + inherits "^2.0.4" + source-map "^0.6.1" + source-map-resolve "^0.6.0" + +cssdb@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-6.2.1.tgz#8904c3f8005bfc334009ee20ddb201330d5a5199" + integrity sha512-TBIhtDCOeYjwr44Vpl1g/224/18lI0jW+PKdA5ZP30dhre3eEutVUb2mnqUnpRPiPWQB7BQf8CWiUGOa966Fnw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^5.1.12: + version "5.1.12" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.12.tgz#64e2ad8e27a279e1413d2d2383ef89a41c909be9" + integrity sha512-rO/JZYyjW1QNkWBxMGV28DW7d98UDLaF759frhli58QFehZ+D/LSmwQ2z/ylBAe2hUlsIWTq6NYGfQPq65EF9w== + dependencies: + css-declaration-sorter "^6.0.3" + cssnano-utils "^3.0.2" + postcss-calc "^8.2.0" + postcss-colormin "^5.2.5" + postcss-convert-values "^5.0.4" + postcss-discard-comments "^5.0.3" + postcss-discard-duplicates "^5.0.3" + postcss-discard-empty "^5.0.3" + postcss-discard-overridden "^5.0.4" + postcss-merge-longhand "^5.0.6" + postcss-merge-rules "^5.0.6" + postcss-minify-font-values "^5.0.4" + postcss-minify-gradients "^5.0.6" + postcss-minify-params "^5.0.5" + postcss-minify-selectors "^5.1.3" + postcss-normalize-charset "^5.0.3" + postcss-normalize-display-values "^5.0.3" + postcss-normalize-positions "^5.0.4" + postcss-normalize-repeat-style "^5.0.4" + postcss-normalize-string "^5.0.4" + postcss-normalize-timing-functions "^5.0.3" + postcss-normalize-unicode "^5.0.4" + postcss-normalize-url "^5.0.5" + postcss-normalize-whitespace "^5.0.4" + postcss-ordered-values "^5.0.5" + postcss-reduce-initial "^5.0.3" + postcss-reduce-transforms "^5.0.4" + postcss-svgo "^5.0.4" + postcss-unique-selectors "^5.0.4" + +cssnano-utils@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.0.2.tgz#d82b4991a27ba6fec644b39bab35fe027137f516" + integrity sha512-KhprijuQv2sP4kT92sSQwhlK3SJTbDIsxcfIEySB0O+3m9esFOai7dP9bMx5enHAh2MwarVIcnwiWoOm01RIbQ== + +cssnano@^5.0.6: + version "5.0.17" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.17.tgz#ff45713c05cfc780a1aeb3e663b6f224d091cabf" + integrity sha512-fmjLP7k8kL18xSspeXTzRhaFtRI7DL9b8IcXR80JgtnWBpvAzHT7sCR/6qdn0tnxIaINUN6OEQu83wF57Gs3Xw== + dependencies: + cssnano-preset-default "^5.1.12" + lilconfig "^2.0.3" + yaml "^1.10.2" + +csso@^4.0.2, csso@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +csstype@^3.0.10, csstype@^3.0.2: + version "3.0.10" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" + integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== + +"d3-color@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.0.1.tgz#03316e595955d1fcd39d9f3610ad41bb90194d0a" + integrity sha512-6/SlHkDOBLyQSJ1j1Ghs82OIUXpKWlR0hCsw0XrLSQhuUPuCSmLQ1QPH98vpnQxMUQM2/gfAkUEWsupVpd9JGw== + +"d3-dispatch@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3": + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-ease@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +"d3-interpolate@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +"d3-timer@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +dagre@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee" + integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw== + dependencies: + graphlib "^2.1.8" + lodash "^4.17.15" + +damerau-levenshtein@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== + +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + +debug@2.6.9, debug@^2.2.0, debug@^2.6.0, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@~4.3.1, debug@~4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + +debug@^3.1.1, debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +decimal.js@^10.2.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + +deep-equal@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + +deep-is@^0.1.3, deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +del@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952" + integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ== + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +detect-port-alt@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" + integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== + dependencies: + address "^1.0.1" + debug "^2.6.0" + +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= + +dns-packet@^1.3.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f" + integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= + dependencies: + buffer-indexof "^1.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: + version "0.5.11" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.11.tgz#79d5846c4f90eba3e617d9031e921de9324f84ed" + integrity sha512-7X6GvzjYf4yTdRKuCVScV+aA9Fvh5r8WzWrXBH9w82ZWB/eYDMGCnazoC/YAqAzUJWHzLOnZqr46K3iEyUhUvw== + +dom-converter@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +domelementtype@1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + +domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" + integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== + dependencies: + domelementtype "^2.2.0" + +domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^2.5.2, domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +ejs@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" + integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== + dependencies: + jake "^10.6.1" + +electron-to-chromium@^1.4.17: + version "1.4.68" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.68.tgz#d79447b6bd1bec9183f166bb33d4bef0d5e4e568" + integrity sha512-cId+QwWrV8R1UawO6b9BR1hnkJ4EJPCPAr4h315vliHUtVUJDk39Sg1PMNnaWKfj5x+93ssjeJ9LKL6r8LaMiA== + +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +engine.io-client@~6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.1.1.tgz#800d4b9db5487d169686729e5bd887afa78d36b0" + integrity sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g== + dependencies: + "@socket.io/component-emitter" "~3.0.0" + debug "~4.3.1" + engine.io-parser "~5.0.0" + has-cors "1.1.0" + parseqs "0.0.6" + parseuri "0.0.6" + ws "~8.2.3" + xmlhttprequest-ssl "~2.0.0" + yeast "0.1.2" + +engine.io-parser@~5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.3.tgz#ca1f0d7b11e290b4bfda251803baea765ed89c09" + integrity sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg== + dependencies: + "@socket.io/base64-arraybuffer" "~1.0.2" + +enhanced-resolve@^5.8.3: + version "5.9.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz#49ac24953ac8452ed8fed2ef1340fc8e043667ee" + integrity sha512-weDYmzbBygL7HzGGS26M3hGQx68vehdEg6VUmqSOaFzXExFqlnKuSvsEJCVGQHScS8CQMbrAqftT+AzzHNt/YA== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +error-stack-parser@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" + integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== + dependencies: + stackframe "^1.1.1" + +es-abstract@^1.17.2, es-abstract@^1.19.0, es-abstract@^1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" + integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.1" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.1" + is-string "^1.0.7" + is-weakref "^1.0.1" + object-inspect "^1.11.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-module-lexer@^0.9.0: + version "0.9.3" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" + integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.53" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + +es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-config-airbnb-base@^15.0.0: + version "15.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" + integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== + dependencies: + confusing-browser-globals "^1.0.10" + object.assign "^4.1.2" + object.entries "^1.1.5" + semver "^6.3.0" + +eslint-config-airbnb@^19.0.4: + version "19.0.4" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz#84d4c3490ad70a0ffa571138ebcdea6ab085fdc3" + integrity sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew== + dependencies: + eslint-config-airbnb-base "^15.0.0" + object.assign "^4.1.2" + object.entries "^1.1.5" + +eslint-config-prettier@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" + integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== + +eslint-config-react-app@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-7.0.0.tgz#0fa96d5ec1dfb99c029b1554362ab3fa1c3757df" + integrity sha512-xyymoxtIt1EOsSaGag+/jmcywRuieQoA2JbPCjnw9HukFj9/97aGPoZVFioaotzk1K5Qt9sHO5EutZbkrAXS0g== + dependencies: + "@babel/core" "^7.16.0" + "@babel/eslint-parser" "^7.16.3" + "@rushstack/eslint-patch" "^1.1.0" + "@typescript-eslint/eslint-plugin" "^5.5.0" + "@typescript-eslint/parser" "^5.5.0" + babel-preset-react-app "^10.0.1" + confusing-browser-globals "^1.0.11" + eslint-plugin-flowtype "^8.0.3" + eslint-plugin-import "^2.25.3" + eslint-plugin-jest "^25.3.0" + eslint-plugin-jsx-a11y "^6.5.1" + eslint-plugin-react "^7.27.1" + eslint-plugin-react-hooks "^4.3.0" + eslint-plugin-testing-library "^5.0.1" + +eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + +eslint-module-utils@^2.7.2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" + integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== + dependencies: + debug "^3.2.7" + find-up "^2.1.0" + +eslint-plugin-flowtype@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz#e1557e37118f24734aa3122e7536a038d34a4912" + integrity sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ== + dependencies: + lodash "^4.17.21" + string-natural-compare "^3.0.1" + +eslint-plugin-import@^2.25.3, eslint-plugin-import@^2.25.4: + version "2.25.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== + dependencies: + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.7.2" + has "^1.0.3" + is-core-module "^2.8.0" + is-glob "^4.0.3" + minimatch "^3.0.4" + object.values "^1.1.5" + resolve "^1.20.0" + tsconfig-paths "^3.12.0" + +eslint-plugin-jest@^25.3.0: + version "25.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz#ff4ac97520b53a96187bad9c9814e7d00de09a6a" + integrity sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ== + dependencies: + "@typescript-eslint/experimental-utils" "^5.0.0" + +eslint-plugin-jsx-a11y@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz#cdbf2df901040ca140b6ec14715c988889c2a6d8" + integrity sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g== + dependencies: + "@babel/runtime" "^7.16.3" + aria-query "^4.2.2" + array-includes "^3.1.4" + ast-types-flow "^0.0.7" + axe-core "^4.3.5" + axobject-query "^2.2.0" + damerau-levenshtein "^1.0.7" + emoji-regex "^9.2.2" + has "^1.0.3" + jsx-ast-utils "^3.2.1" + language-tags "^1.0.5" + minimatch "^3.0.4" + +eslint-plugin-prettier@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz#8b99d1e4b8b24a762472b4567992023619cb98e0" + integrity sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-react-hooks@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172" + integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA== + +eslint-plugin-react@^7.27.1, eslint-plugin-react@^7.28.0: + version "7.28.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz#8f3ff450677571a659ce76efc6d80b6a525adbdf" + integrity sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw== + dependencies: + array-includes "^3.1.4" + array.prototype.flatmap "^1.2.5" + doctrine "^2.1.0" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.0.4" + object.entries "^1.1.5" + object.fromentries "^2.0.5" + object.hasown "^1.1.0" + object.values "^1.1.5" + prop-types "^15.7.2" + resolve "^2.0.0-next.3" + semver "^6.3.0" + string.prototype.matchall "^4.0.6" + +eslint-plugin-testing-library@^5.0.1: + version "5.0.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.0.5.tgz#5757961ec20a6ca8b0992d2c5487db1b51612d8d" + integrity sha512-0j355vJpJCE/2g+aayIgJRUB6jBVqpD5ztMLGcadR1PgrgGPnPxN1HJuOAsAAwiMo27GwRnpJB8KOQzyNuNZrw== + dependencies: + "@typescript-eslint/utils" "^5.10.2" + +eslint-scope@5.1.1, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" + integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" + integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== + +eslint-webpack-plugin@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-3.1.1.tgz#83dad2395e5f572d6f4d919eedaa9cf902890fcb" + integrity sha512-xSucskTN9tOkfW7so4EaiFIkulWLXwCB/15H917lR6pTv0Zot6/fetFucmENRb7J5whVSFKIvwnrnsa78SG2yg== + dependencies: + "@types/eslint" "^7.28.2" + jest-worker "^27.3.1" + micromatch "^4.0.4" + normalize-path "^3.0.0" + schema-utils "^3.1.1" + +eslint@^8.3.0, eslint@^8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.8.0.tgz#9762b49abad0cb4952539ffdb0a046392e571a2d" + integrity sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ== + dependencies: + "@eslint/eslintrc" "^1.0.5" + "@humanwhocodes/config-array" "^0.9.2" + 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.0" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.2.0" + espree "^9.3.0" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^6.0.1" + globals "^13.6.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^9.2.0, espree@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" + integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== + dependencies: + acorn "^8.7.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^3.1.0" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expect@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== + dependencies: + "@jest/types" "^27.5.1" + jest-get-type "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + +express@^4.17.1: + version "4.17.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" + integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.1" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.4.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.9.6" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.17.2" + serve-static "1.14.2" + setprototypeof "1.2.0" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +ext@^1.1.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52" + integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg== + dependencies: + type "^2.5.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-glob@^3.2.11, fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + 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" + +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +filelist@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b" + integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ== + dependencies: + minimatch "^3.0.4" + +filesize@^8.0.6: + version "8.0.7" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-cache-dir@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.5" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" + integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== + +follow-redirects@^1.0.0: + version "1.14.8" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" + integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== + +fork-ts-checker-webpack-plugin@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz#0282b335fa495a97e167f69018f566ea7d2a2b5e" + integrity sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw== + dependencies: + "@babel/code-frame" "^7.8.3" + "@types/json-schema" "^7.0.5" + chalk "^4.1.0" + chokidar "^3.4.2" + cosmiconfig "^6.0.0" + deepmerge "^4.2.2" + fs-extra "^9.0.0" + glob "^7.1.6" + memfs "^3.1.2" + minimatch "^3.0.4" + schema-utils "2.7.0" + semver "^7.3.2" + tapable "^1.0.0" + +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fraction.js@^4.1.2: + version "4.1.3" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.3.tgz#be65b0f20762ef27e1e793860bc2dfb716e99e65" + integrity sha512-pUHWWt6vHzZZiQJcM6S/0PXfS+g6FM4BF5rj9wZyreivhQPdsh5PpE25VtSNxq80wHS5RfY51Ii+8Z0Zl/pmzg== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-extra@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" + integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.0.0, fs-extra@^9.0.1: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-monkey@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" + integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1, glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.6.0, globals@^13.9.0: + version "13.12.1" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.1.tgz#ec206be932e6c77236677127577aa8e50bf1c5cb" + integrity sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw== + dependencies: + type-fest "^0.20.2" + +globby@^11.0.1, globby@^11.0.4: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + 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" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + +graphlib@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" + integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== + dependencies: + lodash "^4.17.15" + +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +harmony-reflect@^1.4.6: + version "1.6.2" + resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" + integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g== + +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-cors@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" + integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hoopy@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" + integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + +html-entities@^2.1.0, html-entities@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488" + integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-minifier-terser@^6.0.2: + version "6.1.0" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" + integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== + dependencies: + camel-case "^4.1.2" + clean-css "^5.2.2" + commander "^8.3.0" + he "^1.2.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.10.0" + +html-webpack-plugin@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" + integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== + dependencies: + "@types/html-minifier-terser" "^6.0.0" + html-minifier-terser "^6.0.2" + lodash "^4.17.21" + pretty-error "^4.0.0" + tapable "^2.0.0" + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + +http-errors@1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.1" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.5.tgz#d7c30d5d3c90d865b4a2e870181f9d6f22ac7ac5" + integrity sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA== + +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +http-proxy-middleware@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.3.tgz#5df04f69a89f530c2284cd71eeaa51ba52243289" + integrity sha512-1bloEwnrHMnCoO/Gcwbz7eSVvW50KPES01PecpagI+YLNLci4AcuKJrujW4Mc3sBLpFxMSlsLNHS5Nl/lvrTPA== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +husky@^7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" + integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== + +hyphenate-style-name@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" + integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + +idb@^6.1.4: + version "6.1.5" + resolved "https://registry.yarnpkg.com/idb/-/idb-6.1.5.tgz#dbc53e7adf1ac7c59f9b2bf56e00b4ea4fce8c7b" + integrity sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw== + +identity-obj-proxy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" + integrity sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ= + dependencies: + harmony-reflect "^1.4.6" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.1.8, ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +immer@^9.0.7: + version "9.0.12" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" + integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== + +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@^1.3.5: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +ip@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +ipaddr.js@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" + integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== + +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-core-module@^2.2.0, is-core-module@^2.8.0, is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-in-browser@^1.0.2, is-in-browser@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" + integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= + +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + +is-negative-zero@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + +is-path-cwd@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + +is-regex@^1.0.4, is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + +is-root@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" + integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== + +is-shared-array-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" + integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-weakref@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" + integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.4" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" + integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jake@^10.6.1: + version "10.8.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" + integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== + dependencies: + async "0.9.x" + chalk "^2.4.2" + filelist "^1.0.1" + minimatch "^3.0.4" + +jest-changed-files@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" + integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== + dependencies: + "@jest/types" "^27.5.1" + execa "^5.0.0" + throat "^6.0.1" + +jest-circus@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" + integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" + integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== + dependencies: + "@jest/core" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + prompts "^2.0.1" + yargs "^16.2.0" + +jest-config@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" + integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== + dependencies: + "@babel/core" "^7.8.0" + "@jest/test-sequencer" "^27.5.1" + "@jest/types" "^27.5.1" + babel-jest "^27.5.1" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.9" + jest-circus "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-get-type "^27.5.1" + jest-jasmine2 "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runner "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^27.5.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^27.0.0, jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-docblock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" + integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== + dependencies: + detect-newline "^3.0.0" + +jest-each@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" + integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== + dependencies: + "@jest/types" "^27.5.1" + chalk "^4.0.0" + jest-get-type "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + +jest-environment-jsdom@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" + integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + jest-util "^27.5.1" + jsdom "^16.6.0" + +jest-environment-node@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" + integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + jest-util "^27.5.1" + +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + +jest-haste-map@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" + integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== + dependencies: + "@jest/types" "^27.5.1" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^27.5.1" + jest-serializer "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + micromatch "^4.0.4" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + +jest-jasmine2@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" + integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + throat "^6.0.1" + +jest-leak-detector@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" + integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== + dependencies: + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-matcher-utils@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-message-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.5.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^27.0.0, jest-regex-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" + integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== + +jest-resolve-dependencies@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" + integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== + dependencies: + "@jest/types" "^27.5.1" + jest-regex-util "^27.5.1" + jest-snapshot "^27.5.1" + +jest-resolve@^27.4.2, jest-resolve@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" + integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== + dependencies: + "@jest/types" "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-pnp-resolver "^1.2.2" + jest-util "^27.5.1" + jest-validate "^27.5.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" + integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.8.1" + graceful-fs "^4.2.9" + jest-docblock "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-haste-map "^27.5.1" + jest-leak-detector "^27.5.1" + jest-message-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runtime "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + source-map-support "^0.5.6" + throat "^6.0.1" + +jest-runtime@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" + integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/globals" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-serializer@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" + integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.9" + +jest-snapshot@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" + integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.0.0" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^27.5.1" + graceful-fs "^4.2.9" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + jest-haste-map "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + natural-compare "^1.4.0" + pretty-format "^27.5.1" + semver "^7.3.2" + +jest-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" + integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== + dependencies: + "@jest/types" "^27.5.1" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^27.5.1" + leven "^3.1.0" + pretty-format "^27.5.1" + +jest-watch-typeahead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-1.0.0.tgz#4de2ca1eb596acb1889752afbab84b74fcd99173" + integrity sha512-jxoszalAb394WElmiJTFBMzie/RDCF+W7Q29n5LzOPtcoQoHWfdUtHFkbhgf5NwWe8uMOxvKb/g7ea7CshfkTw== + dependencies: + ansi-escapes "^4.3.1" + chalk "^4.0.0" + jest-regex-util "^27.0.0" + jest-watcher "^27.0.0" + slash "^4.0.0" + string-length "^5.0.1" + strip-ansi "^7.0.1" + +jest-watcher@^27.0.0, jest-watcher@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" + integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== + dependencies: + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^27.5.1" + string-length "^4.0.1" + +jest-worker@^26.2.1: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest-worker@^27.0.2, jest-worker@^27.3.1, jest-worker@^27.4.5, jest-worker@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^27.4.3: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" + integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== + dependencies: + "@jest/core" "^27.5.1" + import-local "^3.0.2" + jest-cli "^27.5.1" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsdom@^16.6.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== + dependencies: + abab "^2.0.5" + acorn "^8.2.4" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.3.0" + data-urls "^2.0.0" + decimal.js "^10.2.1" + domexception "^2.0.1" + escodegen "^2.0.0" + form-data "^3.0.0" + html-encoding-sniffer "^2.0.1" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.5.0" + ws "^7.4.6" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-schema@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2, json5@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonpointer@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.0.tgz#f802669a524ec4805fa7389eadbc9921d5dc8072" + integrity sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg== + +jss-plugin-camel-case@^10.8.2: + version "10.9.0" + resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz#4921b568b38d893f39736ee8c4c5f1c64670aaf7" + integrity sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww== + dependencies: + "@babel/runtime" "^7.3.1" + hyphenate-style-name "^1.0.3" + jss "10.9.0" + +jss-plugin-default-unit@^10.8.2: + version "10.9.0" + resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz#bb23a48f075bc0ce852b4b4d3f7582bc002df991" + integrity sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.9.0" + +jss-plugin-global@^10.8.2: + version "10.9.0" + resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz#fc07a0086ac97aca174e37edb480b69277f3931f" + integrity sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.9.0" + +jss-plugin-nested@^10.8.2: + version "10.9.0" + resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz#cc1c7d63ad542c3ccc6e2c66c8328c6b6b00f4b3" + integrity sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.9.0" + tiny-warning "^1.0.2" + +jss-plugin-props-sort@^10.8.2: + version "10.9.0" + resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz#30e9567ef9479043feb6e5e59db09b4de687c47d" + integrity sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.9.0" + +jss-plugin-rule-value-function@^10.8.2: + version "10.9.0" + resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz#379fd2732c0746fe45168011fe25544c1a295d67" + integrity sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.9.0" + tiny-warning "^1.0.2" + +jss-plugin-vendor-prefixer@^10.8.2: + version "10.9.0" + resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz#aa9df98abfb3f75f7ed59a3ec50a5452461a206a" + integrity sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA== + dependencies: + "@babel/runtime" "^7.3.1" + css-vendor "^2.0.8" + jss "10.9.0" + +jss@10.9.0, jss@^10.8.2: + version "10.9.0" + resolved "https://registry.yarnpkg.com/jss/-/jss-10.9.0.tgz#7583ee2cdc904a83c872ba695d1baab4b59c141b" + integrity sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw== + dependencies: + "@babel/runtime" "^7.3.1" + csstype "^3.0.2" + is-in-browser "^1.1.3" + tiny-warning "^1.0.2" + +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b" + integrity sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA== + dependencies: + array-includes "^3.1.3" + object.assign "^4.1.2" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +klona@^2.0.4, klona@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" + integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== + +language-subtag-registry@~0.3.2: + version "0.3.21" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" + integrity sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg== + +language-tags@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= + dependencies: + language-subtag-registry "~0.3.2" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lilconfig@^2.0.3, lilconfig@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" + integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +loader-runner@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" + integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== + +loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +loader-utils@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" + integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +loader-utils@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.0.tgz#bcecc51a7898bee7473d4bc6b845b23af8304d4f" + integrity sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ== + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lz-string@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" + integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= + +magic-string@^0.25.0, magic-string@^0.25.7: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +memfs@^3.1.2, memfs@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305" + integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw== + dependencies: + fs-monkey "1.0.3" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + +mime-db@1.51.0, "mime-db@>= 1.43.0 < 2": + version "1.51.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== + +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.34" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== + dependencies: + mime-db "1.51.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +mini-css-extract-plugin@^2.4.5: + version "2.5.3" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.5.3.tgz#c5c79f9b22ce9b4f164e9492267358dbe35376d9" + integrity sha512-YseMB8cs8U/KCaAGQoqYmfUuhhGW0a9p9XvWXrxVOkE3/IiISTLw4ALNt7JR5B2eYauFM+PQGSbXMDmVbR7Tfw== + dependencies: + schema-utils "^4.0.0" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.4: + version "3.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.5.tgz#4da8f1290ee0f0f8e83d60ca69f8f134068604a3" + integrity sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp@^0.5.5, mkdirp@~0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= + +multicast-dns@^6.0.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" + integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== + dependencies: + dns-packet "^1.3.1" + thunky "^1.0.2" + +nanoid@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" + integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +next-tick@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-forge@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c" + integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w== + +node-gyp-build@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" + integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-releases@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" + integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nth-check@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +nth-check@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + dependencies: + boolbase "^1.0.0" + +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + +object-is@^1.0.1: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861" + integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.fromentries@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.5.tgz#7b37b205109c21e741e605727fe8b0ad5fa08251" + integrity sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.getownpropertydescriptors@^2.1.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" + integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.hasown@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.0.tgz#7232ed266f34d197d15cac5880232f7a4790afe5" + integrity sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.values@^1.1.0, object.values@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^8.0.9, open@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + 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" + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-retry@^4.5.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" + integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== + dependencies: + "@types/retry" "^0.12.0" + retry "^0.13.1" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +param-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0, parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +parseqs@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5" + integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w== + +parseuri@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a" + integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow== + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6, path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +picocolors@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" + integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +portfinder@^1.0.28: + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== + dependencies: + async "^2.6.2" + debug "^3.1.1" + mkdirp "^0.5.5" + +postcss-attribute-case-insensitive@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.0.tgz#39cbf6babf3ded1e4abf37d09d6eda21c644105c" + integrity sha512-b4g9eagFGq9T5SWX4+USfVyjIb3liPnjhHHRMP7FMB2kFVpYyfEscV0wP3eaXhKlcHKUut8lt5BGoeylWA/dBQ== + dependencies: + postcss-selector-parser "^6.0.2" + +postcss-browser-comments@^4: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz#bcfc86134df5807f5d3c0eefa191d42136b5e72a" + integrity sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg== + +postcss-calc@^8.2.0: + version "8.2.4" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" + integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== + dependencies: + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" + +postcss-clamp@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-clamp/-/postcss-clamp-3.0.0.tgz#09cb1ad64243b46c9159ded5e8d3e8349150a09e" + integrity sha512-QENQMIF/Grw0qX0RzSPJjw+mAiGPIwG2AnsQDIoR/WJ5Q19zLB0NrZX8cH7CzzdDWEerTPGCdep7ItFaAdtItg== + dependencies: + postcss-value-parser "^4.1.0" + +postcss-color-functional-notation@^4.2.1: + version "4.2.2" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.2.tgz#f59ccaeb4ee78f1b32987d43df146109cc743073" + integrity sha512-DXVtwUhIk4f49KK5EGuEdgx4Gnyj6+t2jBSEmxvpIK9QI40tWrpS2Pua8Q7iIZWBrki2QOaeUdEaLPPa91K0RQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-color-hex-alpha@^8.0.2: + version "8.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.3.tgz#61a0fd151d28b128aa6a8a21a2dad24eebb34d52" + integrity sha512-fESawWJCrBV035DcbKRPAVmy21LpoyiXdPTuHUfWJ14ZRjY7Y7PA6P4g8z6LQGYhU1WAxkTxjIjurXzoe68Glw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-color-rebeccapurple@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.0.2.tgz#5d397039424a58a9ca628762eb0b88a61a66e079" + integrity sha512-SFc3MaocHaQ6k3oZaFwH8io6MdypkUtEy/eXzXEB1vEQlO3S3oDc/FSZA8AsS04Z25RirQhlDlHLh3dn7XewWw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-colormin@^5.2.5: + version "5.2.5" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.5.tgz#d1fc269ac2ad03fe641d462b5d1dada35c69968a" + integrity sha512-+X30aDaGYq81mFqwyPpnYInsZQnNpdxMX0ajlY7AExCexEFkPVV+KrO7kXwayqEWL2xwEbNQ4nUO0ZsRWGnevg== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.4.tgz#3e74dd97c581f475ae7b4500bc0a7c4fb3a6b1b6" + integrity sha512-bugzSAyjIexdObovsPZu/sBCTHccImJxLyFgeV0MmNBm/Lw5h5XnjfML6gzEmJ3A6nyfCW7hb1JXzcsA4Zfbdw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-custom-media@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz#1be6aff8be7dc9bf1fe014bde3b71b92bb4552f1" + integrity sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g== + +postcss-custom-properties@^12.1.4: + version "12.1.4" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-12.1.4.tgz#e3d8a8000f28094453b836dff5132385f2862285" + integrity sha512-i6AytuTCoDLJkWN/MtAIGriJz3j7UX6bV7Z5t+KgFz+dwZS15/mlTJY1S0kRizlk6ba0V8u8hN50Fz5Nm7tdZw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-custom-selectors@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-6.0.0.tgz#022839e41fbf71c47ae6e316cb0e6213012df5ef" + integrity sha512-/1iyBhz/W8jUepjGyu7V1OPcGbc636snN1yXEQCinb6Bwt7KxsiU7/bLQlp8GwAXzCh7cobBU5odNn/2zQWR8Q== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-dir-pseudo-class@^6.0.3: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.4.tgz#9afe49ea631f0cb36fa0076e7c2feb4e7e3f049c" + integrity sha512-I8epwGy5ftdzNWEYok9VjW9whC4xnelAtbajGv4adql4FIF09rnrxnA9Y8xSHN47y7gqFIv10C5+ImsLeJpKBw== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-discard-comments@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.3.tgz#011acb63418d600fdbe18804e1bbecb543ad2f87" + integrity sha512-6W5BemziRoqIdAKT+1QjM4bNcJAQ7z7zk073730NHg4cUXh3/rQHHj7pmYxUB9aGhuRhBiUf0pXvIHkRwhQP0Q== + +postcss-discard-duplicates@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.3.tgz#10f202a4cfe9d407b73dfea7a477054d21ea0c1f" + integrity sha512-vPtm1Mf+kp7iAENTG7jI1MN1lk+fBqL5y+qxyi4v3H+lzsXEdfS3dwUZD45KVhgzDEgduur8ycB4hMegyMTeRw== + +postcss-discard-empty@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.3.tgz#ec185af4a3710b88933b0ff751aa157b6041dd6a" + integrity sha512-xGJugpaXKakwKI7sSdZjUuN4V3zSzb2Y0LOlmTajFbNinEjTfVs9PFW2lmKBaC/E64WwYppfqLD03P8l9BuueA== + +postcss-discard-overridden@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.4.tgz#cc999d6caf18ea16eff8b2b58f48ec3ddee35c9c" + integrity sha512-3j9QH0Qh1KkdxwiZOW82cId7zdwXVQv/gRXYDnwx5pBtR1sTkU4cXRK9lp5dSdiM0r0OICO/L8J6sV1/7m0kHg== + +postcss-double-position-gradients@^3.0.4: + version "3.0.5" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.0.5.tgz#f6b755e9850bb9816dfbf8fa346d9ce2e8a03848" + integrity sha512-XiZzvdxLOWZwtt/1GgHJYGoD9scog/DD/yI5dcvPrXNdNDEv7T53/6tL7ikl+EM3jcerII5/XIQzd1UHOdTi2w== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-env-function@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-4.0.5.tgz#b9614d50abd91e4c88a114644a9766880dabe393" + integrity sha512-gPUJc71ji9XKyl0WSzAalBeEA/89kU+XpffpPxSaaaZ1c48OL36r1Ep5R6+9XAPkIiDlSvVAwP4io12q/vTcvA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-flexbugs-fixes@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz#2028e145313074fc9abe276cb7ca14e5401eb49d" + integrity sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ== + +postcss-focus-visible@^6.0.3: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz#50c9ea9afa0ee657fb75635fabad25e18d76bf9e" + integrity sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-focus-within@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz#5b1d2ec603195f3344b716c0b75f61e44e8d2e20" + integrity sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-font-variant@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" + integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA== + +postcss-gap-properties@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz#6401bb2f67d9cf255d677042928a70a915e6ba60" + integrity sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ== + +postcss-image-set-function@^4.0.5: + version "4.0.6" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-4.0.6.tgz#bcff2794efae778c09441498f40e0c77374870a9" + integrity sha512-KfdC6vg53GC+vPd2+HYzsZ6obmPqOk6HY09kttU19+Gj1nC3S3XBVEXDHxkhxTohgZqzbUb94bKXvKDnYWBm/A== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-initial@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-4.0.1.tgz#529f735f72c5724a0fb30527df6fb7ac54d7de42" + integrity sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ== + +postcss-js@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00" + integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== + dependencies: + camelcase-css "^2.0.1" + +postcss-lab-function@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.0.4.tgz#504747ab2754e046fb01e72779bbb434a05357df" + integrity sha512-TAEW8X/ahMYV33mvLFQARtBPAy1VVJsiR9VVx3Pcbu+zlqQj0EIyJ/Ie1/EwxwIt530CWtEDzzTXBDzfdb+qIQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-load-config@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.1.tgz#2f53a17f2f543d9e63864460af42efdac0d41f87" + integrity sha512-c/9XYboIbSEUZpiD1UQD0IKiUe8n9WHYV7YFe7X7J+ZwCsEKkUJSFWjS9hBU1RR9THR7jMXst8sxiqP0jjo2mg== + dependencies: + lilconfig "^2.0.4" + yaml "^1.10.2" + +postcss-loader@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" + integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== + dependencies: + cosmiconfig "^7.0.0" + klona "^2.0.5" + semver "^7.3.5" + +postcss-logical@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-5.0.4.tgz#ec75b1ee54421acc04d5921576b7d8db6b0e6f73" + integrity sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g== + +postcss-media-minmax@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5" + integrity sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ== + +postcss-merge-longhand@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.6.tgz#090e60d5d3b3caad899f8774f8dccb33217d2166" + integrity sha512-rkmoPwQO6ymJSmWsX6l2hHeEBQa7C4kJb9jyi5fZB1sE8nSCv7sqchoYPixRwX/yvLoZP2y6FA5kcjiByeJqDg== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^5.0.3" + +postcss-merge-rules@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.6.tgz#26b37411fe1e80202fcef61cab027265b8925f2b" + integrity sha512-nzJWJ9yXWp8AOEpn/HFAW72WKVGD2bsLiAmgw4hDchSij27bt6TF+sIK0cJUBAYT3SGcjtGGsOR89bwkkMuMgQ== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + cssnano-utils "^3.0.2" + postcss-selector-parser "^6.0.5" + +postcss-minify-font-values@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.4.tgz#627d824406b0712243221891f40a44fffe1467fd" + integrity sha512-RN6q3tyuEesvyCYYFCRGJ41J1XFvgV+dvYGHr0CeHv8F00yILlN8Slf4t8XW4IghlfZYCeyRrANO6HpJ948ieA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.6.tgz#b07cef51a93f075e94053fd972ff1cba2eaf6503" + integrity sha512-E/dT6oVxB9nLGUTiY/rG5dX9taugv9cbLNTFad3dKxOO+BQg25Q/xo2z2ddG+ZB1CbkZYaVwx5blY8VC7R/43A== + dependencies: + colord "^2.9.1" + cssnano-utils "^3.0.2" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.5.tgz#86cb624358cd45c21946f8c317893f0449396646" + integrity sha512-YBNuq3Rz5LfLFNHb9wrvm6t859b8qIqfXsWeK7wROm3jSKNpO1Y5e8cOyBv6Acji15TgSrAwb3JkVNCqNyLvBg== + dependencies: + browserslist "^4.16.6" + cssnano-utils "^3.0.2" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.3.tgz#6ac12d52aa661fd509469d87ab2cebb0a1e3a1b5" + integrity sha512-9RJfTiQEKA/kZhMaEXND893nBqmYQ8qYa/G+uPdVnXF6D/FzpfI6kwBtWEcHx5FqDbA79O9n6fQJfrIj6M8jvQ== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" + integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" + integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-nested@5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" + integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA== + dependencies: + postcss-selector-parser "^6.0.6" + +postcss-nesting@^10.1.2: + version "10.1.2" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.1.2.tgz#2e5f811b3d75602ea18a95dd445bde5297145141" + integrity sha512-dJGmgmsvpzKoVMtDMQQG/T6FSqs6kDtUDirIfl4KnjMCiY9/ETX8jdKyCd20swSRAbUYkaBKV20pxkzxoOXLqQ== + dependencies: + postcss-selector-parser "^6.0.8" + +postcss-normalize-charset@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.3.tgz#719fb9f9ca9835fcbd4fed8d6e0d72a79e7b5472" + integrity sha512-iKEplDBco9EfH7sx4ut7R2r/dwTnUqyfACf62Unc9UiyFuI7uUqZZtY+u+qp7g8Qszl/U28HIfcsI3pEABWFfA== + +postcss-normalize-display-values@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.3.tgz#94cc82e20c51cc4ffba6b36e9618adc1e50db8c1" + integrity sha512-FIV5FY/qs4Ja32jiDb5mVj5iWBlS3N8tFcw2yg98+8MkRgyhtnBgSC0lxU+16AMHbjX5fbSJgw5AXLMolonuRQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-positions@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.4.tgz#4001f38c99675437b83277836fb4291887fcc6cc" + integrity sha512-qynirjBX0Lc73ROomZE3lzzmXXTu48/QiEzKgMeqh28+MfuHLsuqC9po4kj84igZqqFGovz8F8hf44hA3dPYmQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-repeat-style@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.4.tgz#d005adf9ee45fae78b673031a376c0c871315145" + integrity sha512-Innt+wctD7YpfeDR7r5Ik6krdyppyAg2HBRpX88fo5AYzC1Ut/l3xaxACG0KsbX49cO2n5EB13clPwuYVt8cMA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-string@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.4.tgz#b5e00a07597e7aa8a871817bfeac2bfaa59c3333" + integrity sha512-Dfk42l0+A1CDnVpgE606ENvdmksttLynEqTQf5FL3XGQOyqxjbo25+pglCUvziicTxjtI2NLUR6KkxyUWEVubQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-timing-functions@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.3.tgz#47210227bfcba5e52650d7a18654337090de7072" + integrity sha512-QRfjvFh11moN4PYnJ7hia4uJXeFotyK3t2jjg8lM9mswleGsNw2Lm3I5wO+l4k1FzK96EFwEVn8X8Ojrp2gP4g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-unicode@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.4.tgz#02866096937005cdb2c17116c690f29505a1623d" + integrity sha512-W79Regn+a+eXTzB+oV/8XJ33s3pDyFTND2yDuUCo0Xa3QSy1HtNIfRVPXNubHxjhlqmMFADr3FSCHT84ITW3ig== + dependencies: + browserslist "^4.16.6" + postcss-value-parser "^4.2.0" + +postcss-normalize-url@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.5.tgz#c39efc12ff119f6f45f0b4f516902b12c8080e3a" + integrity sha512-Ws3tX+PcekYlXh+ycAt0wyzqGthkvVtZ9SZLutMVvHARxcpu4o7vvXcNoiNKyjKuWecnjS6HDI3fjBuDr5MQxQ== + dependencies: + normalize-url "^6.0.1" + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.4.tgz#1d477e7da23fecef91fc4e37d462272c7b55c5ca" + integrity sha512-wsnuHolYZjMwWZJoTC9jeI2AcjA67v4UuidDrPN9RnX8KIZfE+r2Nd6XZRwHVwUiHmRvKQtxiqo64K+h8/imaw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize/-/postcss-normalize-10.0.1.tgz#464692676b52792a06b06880a176279216540dd7" + integrity sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA== + dependencies: + "@csstools/normalize.css" "*" + postcss-browser-comments "^4" + sanitize.css "*" + +postcss-opacity-percentage@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz#bd698bb3670a0a27f6d657cc16744b3ebf3b1145" + integrity sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w== + +postcss-ordered-values@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.5.tgz#e878af822a130c3f3709737e24cb815ca7c6d040" + integrity sha512-mfY7lXpq+8bDEHfP+muqibDPhZ5eP9zgBEF9XRvoQgXcQe2Db3G1wcvjbnfjXG6wYsl+0UIjikqq4ym1V2jGMQ== + dependencies: + cssnano-utils "^3.0.2" + postcss-value-parser "^4.2.0" + +postcss-overflow-shorthand@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz#ebcfc0483a15bbf1b27fdd9b3c10125372f4cbc2" + integrity sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg== + +postcss-page-break@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" + integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ== + +postcss-place@^7.0.3: + version "7.0.4" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-7.0.4.tgz#eb026650b7f769ae57ca4f938c1addd6be2f62c9" + integrity sha512-MrgKeiiu5OC/TETQO45kV3npRjOFxEHthsqGtkh3I1rPbZSbXGD/lZVi9j13cYh+NA8PIAPyk6sGjT9QbRyvSg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-preset-env@^7.0.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-7.3.1.tgz#f17c609cfab3432620b92888464f92b4dba5eca0" + integrity sha512-x7fNsJxfkY60P4FUNwhJUOfXBFfnObd2EcUYY97sXZ0XRLgmAE65es9EFIYHq1rAk7X3LMfbG+L9wYgkrNsq5Q== + dependencies: + "@csstools/postcss-font-format-keywords" "^1.0.0" + "@csstools/postcss-hwb-function" "^1.0.0" + "@csstools/postcss-is-pseudo-class" "^2.0.0" + "@csstools/postcss-normalize-display-values" "^1.0.0" + autoprefixer "^10.4.2" + browserslist "^4.19.1" + css-blank-pseudo "^3.0.2" + css-has-pseudo "^3.0.3" + css-prefers-color-scheme "^6.0.3" + cssdb "^6.1.0" + postcss-attribute-case-insensitive "^5.0.0" + postcss-clamp "^3.0.0" + postcss-color-functional-notation "^4.2.1" + postcss-color-hex-alpha "^8.0.2" + postcss-color-rebeccapurple "^7.0.2" + postcss-custom-media "^8.0.0" + postcss-custom-properties "^12.1.4" + postcss-custom-selectors "^6.0.0" + postcss-dir-pseudo-class "^6.0.3" + postcss-double-position-gradients "^3.0.4" + postcss-env-function "^4.0.4" + postcss-focus-visible "^6.0.3" + postcss-focus-within "^5.0.3" + postcss-font-variant "^5.0.0" + postcss-gap-properties "^3.0.2" + postcss-image-set-function "^4.0.5" + postcss-initial "^4.0.1" + postcss-lab-function "^4.0.3" + postcss-logical "^5.0.3" + postcss-media-minmax "^5.0.0" + postcss-nesting "^10.1.2" + postcss-opacity-percentage "^1.1.2" + postcss-overflow-shorthand "^3.0.2" + postcss-page-break "^3.0.4" + postcss-place "^7.0.3" + postcss-pseudo-class-any-link "^7.1.0" + postcss-replace-overflow-wrap "^4.0.0" + postcss-selector-not "^5.0.0" + +postcss-pseudo-class-any-link@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.1.tgz#534eb1dadd9945eb07830dbcc06fb4d5d865b8e0" + integrity sha512-JRoLFvPEX/1YTPxRxp1JO4WxBVXJYrSY7NHeak5LImwJ+VobFMwYDQHvfTXEpcn+7fYIeGkC29zYFhFWIZD8fg== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-reduce-initial@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.3.tgz#68891594defd648253703bbd8f1093162f19568d" + integrity sha512-c88TkSnQ/Dnwgb4OZbKPOBbCaauwEjbECP5uAuFPOzQ+XdjNjRH7SG0dteXrpp1LlIFEKK76iUGgmw2V0xeieA== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.4.tgz#717e72d30befe857f7d2784dba10eb1157863712" + integrity sha512-VIJB9SFSaL8B/B7AXb7KHL6/GNNbbCHslgdzS9UDfBZYIA2nx8NLY7iD/BXFSO/1sRUILzBTfHCoW5inP37C5g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-replace-overflow-wrap@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" + integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== + +postcss-selector-not@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-5.0.0.tgz#ac5fc506f7565dd872f82f5314c0f81a05630dc7" + integrity sha512-/2K3A4TCP9orP4TNS7u3tGdRFVKqz/E6pX3aGnriPG0jU78of8wsUcqE4QAhWEU0d+WnMSF93Ah3F//vUtK+iQ== + dependencies: + balanced-match "^1.0.0" + +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.8, postcss-selector-parser@^6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" + integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-svgo@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.4.tgz#cfa8682f47b88f7cd75108ec499e133b43102abf" + integrity sha512-yDKHvULbnZtIrRqhZoA+rxreWpee28JSRH/gy9727u0UCgtpv1M/9WEWY3xySlFa0zQJcqf6oCBJPR5NwkmYpg== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^2.7.0" + +postcss-unique-selectors@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.4.tgz#08e188126b634ddfa615fb1d6c262bafdd64826e" + integrity sha512-5ampwoSDJCxDPoANBIlMgoBcYUHnhaiuLYJR5pj1DLnYQvMRVyFuTA5C3Bvt+aHtiqWpJkD/lXT50Vo1D0ZsAQ== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^7.0.35: + version "7.0.39" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" + integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== + dependencies: + picocolors "^0.2.1" + source-map "^0.6.1" + +postcss@^8.3.5, postcss@^8.4.4, postcss@^8.4.5: + version "8.4.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1" + integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA== + dependencies: + nanoid "^3.2.0" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" + integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== + +pretty-bytes@^5.3.0, pretty-bytes@^5.4.1: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + +pretty-error@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" + integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== + dependencies: + lodash "^4.17.20" + renderkid "^3.0.0" + +pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +promise@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" + integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q== + dependencies: + asap "~2.0.6" + +prompts@^2.0.1, prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +psl@^1.1.33: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qs@6.9.6: + version "6.9.6" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" + integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" + integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== + dependencies: + bytes "3.1.1" + http-errors "1.8.1" + iconv-lite "0.4.24" + unpipe "1.0.0" + +react-app-polyfill@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz#95221e0a9bd259e5ca6b177c7bb1cb6768f68fd7" + integrity sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w== + dependencies: + core-js "^3.19.2" + object-assign "^4.1.1" + promise "^8.1.0" + raf "^3.4.1" + regenerator-runtime "^0.13.9" + whatwg-fetch "^3.6.2" + +react-dev-utils@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526" + integrity sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ== + dependencies: + "@babel/code-frame" "^7.16.0" + address "^1.1.2" + browserslist "^4.18.1" + chalk "^4.1.2" + cross-spawn "^7.0.3" + detect-port-alt "^1.1.6" + escape-string-regexp "^4.0.0" + filesize "^8.0.6" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.5.0" + global-modules "^2.0.0" + globby "^11.0.4" + gzip-size "^6.0.0" + immer "^9.0.7" + is-root "^2.1.0" + loader-utils "^3.2.0" + open "^8.4.0" + pkg-up "^3.1.0" + prompts "^2.4.2" + react-error-overlay "^6.0.10" + recursive-readdir "^2.2.2" + shell-quote "^1.7.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.2" + +react-draggable@^4.4.4: + version "4.4.4" + resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.4.tgz#5b26d9996be63d32d285a426f41055de87e59b2f" + integrity sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA== + dependencies: + clsx "^1.1.1" + prop-types "^15.6.0" + +react-error-overlay@^6.0.10: + version "6.0.10" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" + integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== + +react-flow-renderer@^9.7.4: + version "9.7.4" + resolved "https://registry.yarnpkg.com/react-flow-renderer/-/react-flow-renderer-9.7.4.tgz#11394c05ca953b650e2017d056c075fd3df9075c" + integrity sha512-GxHBXzkn8Y+TEG8pul7h6Fjo4cKrT0kW9UQ34OAGZqAnSBLbBsx9W++TF8GiULBbTn3O8o7HtHxux685Op10mQ== + dependencies: + "@babel/runtime" "^7.16.7" + classcat "^5.0.3" + d3-selection "^3.0.0" + d3-zoom "^3.0.0" + fast-deep-equal "^3.1.3" + react-draggable "^4.4.4" + react-redux "^7.2.6" + redux "^4.1.2" + +react-is@^16.13.1, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.1, react-is@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-redux@^7.2.6: + version "7.2.6" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.6.tgz#49633a24fe552b5f9caf58feb8a138936ddfe9aa" + integrity sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/react-redux" "^7.1.20" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^17.0.2" + +react-refresh@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" + integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== + +react-scripts@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.0.tgz#6547a6d7f8b64364ef95273767466cc577cb4b60" + integrity sha512-3i0L2CyIlROz7mxETEdfif6Sfhh9Lfpzi10CtcGs1emDQStmZfWjJbAIMtRD0opVUjQuFWqHZyRZ9PPzKCFxWg== + dependencies: + "@babel/core" "^7.16.0" + "@pmmmwh/react-refresh-webpack-plugin" "^0.5.3" + "@svgr/webpack" "^5.5.0" + babel-jest "^27.4.2" + babel-loader "^8.2.3" + babel-plugin-named-asset-import "^0.3.8" + babel-preset-react-app "^10.0.1" + bfj "^7.0.2" + browserslist "^4.18.1" + camelcase "^6.2.1" + case-sensitive-paths-webpack-plugin "^2.4.0" + css-loader "^6.5.1" + css-minimizer-webpack-plugin "^3.2.0" + dotenv "^10.0.0" + dotenv-expand "^5.1.0" + eslint "^8.3.0" + eslint-config-react-app "^7.0.0" + eslint-webpack-plugin "^3.1.1" + file-loader "^6.2.0" + fs-extra "^10.0.0" + html-webpack-plugin "^5.5.0" + identity-obj-proxy "^3.0.0" + jest "^27.4.3" + jest-resolve "^27.4.2" + jest-watch-typeahead "^1.0.0" + mini-css-extract-plugin "^2.4.5" + postcss "^8.4.4" + postcss-flexbugs-fixes "^5.0.2" + postcss-loader "^6.2.1" + postcss-normalize "^10.0.1" + postcss-preset-env "^7.0.1" + prompts "^2.4.2" + react-app-polyfill "^3.0.0" + react-dev-utils "^12.0.0" + react-refresh "^0.11.0" + resolve "^1.20.0" + resolve-url-loader "^4.0.0" + sass-loader "^12.3.0" + semver "^7.3.5" + source-map-loader "^3.0.0" + style-loader "^3.3.1" + tailwindcss "^3.0.2" + terser-webpack-plugin "^5.2.5" + webpack "^5.64.4" + webpack-dev-server "^4.6.0" + webpack-manifest-plugin "^4.0.2" + workbox-webpack-plugin "^6.4.1" + optionalDependencies: + fsevents "^2.3.2" + +react-transition-group@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470" + integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +readable-stream@^2.0.1: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +recursive-readdir@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" + integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== + dependencies: + minimatch "3.0.4" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +redux-thunk@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" + integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== + +redux@^4.0.0, redux@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104" + integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw== + dependencies: + "@babel/runtime" "^7.9.2" + +regenerate-unicode-properties@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" + integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.9: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regenerator-transform@^0.14.2: + version "0.14.5" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" + integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== + dependencies: + "@babel/runtime" "^7.8.4" + +regex-parser@^2.2.11: + version "2.2.11" + resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58" + integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== + +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" + integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +regexpu-core@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.0.1.tgz#c531122a7840de743dcf9c83e923b5560323ced3" + integrity sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.0.0" + +regjsgen@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" + integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== + +regjsparser@^0.8.2: + version "0.8.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" + integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== + dependencies: + jsesc "~0.5.0" + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= + +renderkid@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" + integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== + dependencies: + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^6.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +reselect@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.5.tgz#852c361247198da6756d07d9296c2b51eddb79f6" + integrity sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ== + +resize-observer-polyfill@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-url-loader@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz#d50d4ddc746bb10468443167acf800dcd6c3ad57" + integrity sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA== + dependencies: + adjust-sourcemap-loader "^4.0.0" + convert-source-map "^1.7.0" + loader-utils "^2.0.0" + postcss "^7.0.35" + source-map "0.6.1" + +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + dependencies: + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.3: + version "2.0.0-next.3" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" + integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rifm@^0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.12.1.tgz#8fa77f45b7f1cda2a0068787ac821f0593967ac4" + integrity sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg== + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup-plugin-terser@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" + integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ== + dependencies: + "@babel/code-frame" "^7.10.4" + jest-worker "^26.2.1" + serialize-javascript "^4.0.0" + terser "^5.0.0" + +rollup@^2.43.1: + version "2.67.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.67.1.tgz#4402665706fa00f321d446ce45f880e02cf54f01" + integrity sha512-1Sbcs4OuW+aD+hhqpIRl+RqooIpF6uQcfzU/QSI7vGkwADY6cM4iLsBGRM2CGLXDTDN5y/yShohFmnKegSPWzg== + optionalDependencies: + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sanitize.css@*: + version "13.0.0" + resolved "https://registry.yarnpkg.com/sanitize.css/-/sanitize.css-13.0.0.tgz#2675553974b27964c75562ade3bd85d79879f173" + integrity sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA== + +sass-loader@^12.3.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.4.0.tgz#260b0d51a8a373bb8e88efc11f6ba5583fea0bcf" + integrity sha512-7xN+8khDIzym1oL9XyS6zP6Ges+Bo2B2xbPrjdMHEYyV3AQYhd/wXeru++3ODHF0zMjYmVadblSKrPrjEkL8mg== + dependencies: + klona "^2.0.4" + neo-async "^2.6.2" + +sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +schema-utils@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== + dependencies: + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" + ajv-keywords "^3.4.1" + +schema-utils@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" + integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.8.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.0.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= + +selfsigned@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.0.tgz#e927cd5377cbb0a1075302cff8df1042cc2bce5b" + integrity sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ== + dependencies: + node-forge "^1.2.0" + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.2, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +send@0.17.2: + version "0.17.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" + integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "1.8.1" + mime "1.6.0" + ms "2.1.3" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.14.2: + version "1.14.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" + integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.2" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" + integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + +socket.io-client@*, socket.io-client@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.4.1.tgz#b6aa9448149d09b8d0b2bbf3d2fac310631fdec9" + integrity sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ== + dependencies: + "@socket.io/component-emitter" "~3.0.0" + backo2 "~1.0.2" + debug "~4.3.2" + engine.io-client "~6.1.1" + parseuri "0.0.6" + socket.io-parser "~4.1.1" + +socket.io-parser@~4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.1.1.tgz#0ad53d980781cab1eabe320417d8480c0133e62d" + integrity sha512-USQVLSkDWE5nbcY760ExdKaJxCE65kcsG/8k5FDGZVVxpD1pA7hABYXYkCUvxUuYYh/+uQw0N/fvBzfT8o07KA== + dependencies: + "@socket.io/component-emitter" "~3.0.0" + debug "~4.3.1" + +sockjs@^0.3.21: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +source-list-map@^2.0.0, source-list-map@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-js@^1.0.1, source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map-loader@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-3.0.1.tgz#9ae5edc7c2d42570934be4c95d1ccc6352eba52d" + integrity sha512-Vp1UsfyPvgujKQzi4pyDiTOnE3E4H+yHvkVRN3c/9PJmQS4CQJExvcDvaX/D+RV+xQben9HJ56jMJS3CgUeWyA== + dependencies: + abab "^2.0.5" + iconv-lite "^0.6.3" + source-map-js "^1.0.1" + +source-map-resolve@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" + integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + +source-map-support@^0.5.6, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.5.0, source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.7.3, source-map@~0.7.2: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +source-map@^0.8.0-beta.0: + version "0.8.0-beta.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" + integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== + dependencies: + whatwg-url "^7.0.0" + +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" + +stackframe@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" + integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== + +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-length@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-5.0.1.tgz#3d647f497b6e8e8d41e422f7e0b23bc536c8381e" + integrity sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow== + dependencies: + char-regex "^2.0.0" + strip-ansi "^7.0.1" + +string-natural-compare@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" + integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string.prototype.matchall@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz#5abb5dabc94c7b0ea2380f65ba610b3a544b15fa" + integrity sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + regexp.prototype.flags "^1.3.1" + side-channel "^1.0.4" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.0, strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-comments@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-2.0.1.tgz#4ad11c3fbcac177a67a40ac224ca339ca1c1ba9b" + integrity sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +style-loader@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" + integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== + +stylehacks@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.3.tgz#2ef3de567bfa2be716d29a93bf3d208c133e8d04" + integrity sha512-ENcUdpf4yO0E1rubu8rkxI+JGQk4CgjchynZ4bDBJDfqdy+uhTRSWb8/F3Jtu+Bw5MW45Po3/aQGeIyyxgQtxg== + dependencies: + browserslist "^4.16.6" + postcss-selector-parser "^6.0.4" + +stylis@4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" + integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svg-parser@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + +svgo@^1.2.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + +svgo@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +tailwindcss@^3.0.2: + version "3.0.19" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.19.tgz#cd789953e6762af2e80c5a3e5d6da3a975ee8215" + integrity sha512-rjsdfz/qZya5xQ0OVynEMETgWq1CacmftgMYeXXh6bRM5vxsNwRSbMJsCCIjq/w67om9VP/AFMolOwiE+5VKig== + dependencies: + arg "^5.0.1" + chalk "^4.1.2" + chokidar "^3.5.3" + color-name "^1.1.4" + cosmiconfig "^7.0.1" + detective "^5.2.0" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.11" + glob-parent "^6.0.2" + is-glob "^4.0.3" + normalize-path "^3.0.0" + object-hash "^2.2.0" + postcss-js "^4.0.0" + postcss-load-config "^3.1.0" + postcss-nested "5.0.6" + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" + quick-lru "^5.1.1" + resolve "^1.22.0" + +tapable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + +tempy@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.6.0.tgz#65e2c35abc06f1124a97f387b08303442bde59f3" + integrity sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw== + dependencies: + is-stream "^2.0.0" + temp-dir "^2.0.0" + type-fest "^0.16.0" + unique-string "^2.0.0" + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.5: + version "5.3.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz#0320dcc270ad5372c1e8993fabbd927929773e54" + integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g== + dependencies: + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + terser "^5.7.2" + +terser@^5.0.0, terser@^5.10.0, terser@^5.7.2: + version "5.10.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc" + integrity sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + dependencies: + punycode "^2.1.0" + +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + +tryer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" + integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== + +tsconfig-paths@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" + integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.3: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" + integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.6.0.tgz#3ca6099af5981d36ca86b78442973694278a219f" + integrity sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^4.4.2: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== + +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" + integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" + integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + +universalify@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +upath@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +utf-8-validate@^5.0.2: + version "5.0.8" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.8.tgz#4a735a61661dbb1c59a0868c397d2fe263f14e58" + integrity sha512-k4dW/Qja1BYDl2qD4tOMB9PFVha/UJtxTc1cXYOe3WwA/2m0Yn4qB7wLMpJyLJ/7DR0XnTut3HsCSzDT4ZvKgA== + dependencies: + node-gyp-build "^4.3.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +v8-to-istanbul@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" + integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + +walker@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +watchpack@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" + integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +web-vitals@^2.1.0: + version "2.1.4" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c" + integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg== + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +webpack-dev-middleware@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz#aa079a8dedd7e58bfeab358a9af7dab304cee57f" + integrity sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg== + dependencies: + colorette "^2.0.10" + memfs "^3.4.1" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@^4.6.0: + version "4.7.4" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz#d0ef7da78224578384e795ac228d8efb63d5f945" + integrity sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.2.2" + ansi-html-community "^0.0.8" + bonjour "^3.5.0" + chokidar "^3.5.3" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^1.6.0" + default-gateway "^6.0.3" + del "^6.0.0" + express "^4.17.1" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.0" + ipaddr.js "^2.0.1" + open "^8.0.9" + p-retry "^4.5.0" + portfinder "^1.0.28" + schema-utils "^4.0.0" + selfsigned "^2.0.0" + serve-index "^1.9.1" + sockjs "^0.3.21" + spdy "^4.0.2" + strip-ansi "^7.0.0" + webpack-dev-middleware "^5.3.1" + ws "^8.4.2" + +webpack-manifest-plugin@^4.0.2: + version "4.1.1" + resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz#10f8dbf4714ff93a215d5a45bcc416d80506f94f" + integrity sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow== + dependencies: + tapable "^2.0.0" + webpack-sources "^2.2.0" + +webpack-sources@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack-sources@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.3.1.tgz#570de0af163949fe272233c2cefe1b56f74511fd" + integrity sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA== + dependencies: + source-list-map "^2.0.1" + source-map "^0.6.1" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.64.4: + version "5.68.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.68.0.tgz#a653a58ed44280062e47257f260117e4be90d560" + integrity sha512-zUcqaUO0772UuuW2bzaES2Zjlm/y3kRBQDVFVCge+s2Y8mwuUTdperGaAv65/NtRL/1zanpSJOq/MD8u61vo6g== + dependencies: + "@types/eslint-scope" "^3.7.0" + "@types/estree" "^0.0.50" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.4.1" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.8.3" + 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-better-errors "^1.0.2" + 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.3.1" + webpack-sources "^3.2.3" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +websocket@^1.0.34: + version "1.0.34" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" + integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.50" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-fetch@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" + integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== + +whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3, word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +workbox-background-sync@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.4.2.tgz#bb31b95928d376abcb9bde0de3a0cef9bae46cf7" + integrity sha512-P7c8uG5X2k+DMICH9xeSA9eUlCOjHHYoB42Rq+RtUpuwBxUOflAXR1zdsMWj81LopE4gjKXlTw7BFd1BDAHo7g== + dependencies: + idb "^6.1.4" + workbox-core "6.4.2" + +workbox-broadcast-update@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.4.2.tgz#5094c4767dfb590532ac03ee07e9e82b2ac206bc" + integrity sha512-qnBwQyE0+PWFFc/n4ISXINE49m44gbEreJUYt2ldGH3+CNrLmJ1egJOOyUqqu9R4Eb7QrXcmB34ClXG7S37LbA== + dependencies: + workbox-core "6.4.2" + +workbox-build@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.4.2.tgz#47f9baa946c3491533cd5ccb1f194a7160e8a6e3" + integrity sha512-WMdYLhDIsuzViOTXDH+tJ1GijkFp5khSYolnxR/11zmfhNDtuo7jof72xPGFy+KRpsz6tug39RhivCj77qqO0w== + dependencies: + "@apideck/better-ajv-errors" "^0.3.1" + "@babel/core" "^7.11.1" + "@babel/preset-env" "^7.11.0" + "@babel/runtime" "^7.11.2" + "@rollup/plugin-babel" "^5.2.0" + "@rollup/plugin-node-resolve" "^11.2.1" + "@rollup/plugin-replace" "^2.4.1" + "@surma/rollup-plugin-off-main-thread" "^2.2.3" + ajv "^8.6.0" + common-tags "^1.8.0" + fast-json-stable-stringify "^2.1.0" + fs-extra "^9.0.1" + glob "^7.1.6" + lodash "^4.17.20" + pretty-bytes "^5.3.0" + rollup "^2.43.1" + rollup-plugin-terser "^7.0.0" + source-map "^0.8.0-beta.0" + source-map-url "^0.4.0" + stringify-object "^3.3.0" + strip-comments "^2.0.1" + tempy "^0.6.0" + upath "^1.2.0" + workbox-background-sync "6.4.2" + workbox-broadcast-update "6.4.2" + workbox-cacheable-response "6.4.2" + workbox-core "6.4.2" + workbox-expiration "6.4.2" + workbox-google-analytics "6.4.2" + workbox-navigation-preload "6.4.2" + workbox-precaching "6.4.2" + workbox-range-requests "6.4.2" + workbox-recipes "6.4.2" + workbox-routing "6.4.2" + workbox-strategies "6.4.2" + workbox-streams "6.4.2" + workbox-sw "6.4.2" + workbox-window "6.4.2" + +workbox-cacheable-response@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.4.2.tgz#ebcabb3667019da232e986a9927af97871e37ccb" + integrity sha512-9FE1W/cKffk1AJzImxgEN0ceWpyz1tqNjZVtA3/LAvYL3AC5SbIkhc7ZCO82WmO9IjTfu8Vut2X/C7ViMSF7TA== + dependencies: + workbox-core "6.4.2" + +workbox-core@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.4.2.tgz#f99fd36a211cc01dce90aa7d5f2c255e8fe9d6bc" + integrity sha512-1U6cdEYPcajRXiboSlpJx6U7TvhIKbxRRerfepAJu2hniKwJ3DHILjpU/zx3yvzSBCWcNJDoFalf7Vgd7ey/rw== + +workbox-expiration@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.4.2.tgz#61613459fd6ddd1362730767618d444c6b9c9139" + integrity sha512-0hbpBj0tDnW+DZOUmwZqntB/8xrXOgO34i7s00Si/VlFJvvpRKg1leXdHHU8ykoSBd6+F2KDcMP3swoCi5guLw== + dependencies: + idb "^6.1.4" + workbox-core "6.4.2" + +workbox-google-analytics@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.4.2.tgz#eea7d511b3078665a726dc2ee9f11c6b7a897530" + integrity sha512-u+gxs3jXovPb1oul4CTBOb+T9fS1oZG+ZE6AzS7l40vnyfJV79DaLBvlpEZfXGv3CjMdV1sT/ltdOrKzo7HcGw== + dependencies: + workbox-background-sync "6.4.2" + workbox-core "6.4.2" + workbox-routing "6.4.2" + workbox-strategies "6.4.2" + +workbox-navigation-preload@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.4.2.tgz#35cd4ba416a530796af135410ca07db5bee11668" + integrity sha512-viyejlCtlKsbJCBHwhSBbWc57MwPXvUrc8P7d+87AxBGPU+JuWkT6nvBANgVgFz6FUhCvRC8aYt+B1helo166g== + dependencies: + workbox-core "6.4.2" + +workbox-precaching@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.4.2.tgz#8d87c05d54f32ac140f549faebf3b4d42d63621e" + integrity sha512-CZ6uwFN/2wb4noHVlALL7UqPFbLfez/9S2GAzGAb0Sk876ul9ukRKPJJ6gtsxfE2HSTwqwuyNVa6xWyeyJ1XSA== + dependencies: + workbox-core "6.4.2" + workbox-routing "6.4.2" + workbox-strategies "6.4.2" + +workbox-range-requests@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.4.2.tgz#050f0dfbb61cd1231e609ed91298b6c2442ae41b" + integrity sha512-SowF3z69hr3Po/w7+xarWfzxJX/3Fo0uSG72Zg4g5FWWnHpq2zPvgbWerBZIa81zpJVUdYpMa3akJJsv+LaO1Q== + dependencies: + workbox-core "6.4.2" + +workbox-recipes@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.4.2.tgz#68de41fa3a77b444b0f93c9c01a76ba1d41fd2bf" + integrity sha512-/oVxlZFpAjFVbY+3PoGEXe8qyvtmqMrTdWhbOfbwokNFtUZ/JCtanDKgwDv9x3AebqGAoJRvQNSru0F4nG+gWA== + dependencies: + workbox-cacheable-response "6.4.2" + workbox-core "6.4.2" + workbox-expiration "6.4.2" + workbox-precaching "6.4.2" + workbox-routing "6.4.2" + workbox-strategies "6.4.2" + +workbox-routing@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.4.2.tgz#65b1c61e8ca79bb9152f93263c26b1f248d09dcc" + integrity sha512-0ss/n9PAcHjTy4Ad7l2puuod4WtsnRYu9BrmHcu6Dk4PgWeJo1t5VnGufPxNtcuyPGQ3OdnMdlmhMJ57sSrrSw== + dependencies: + workbox-core "6.4.2" + +workbox-strategies@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.4.2.tgz#50c02bf2d116918e1a8052df5f2c1e4103c62d5d" + integrity sha512-YXh9E9dZGEO1EiPC3jPe2CbztO5WT8Ruj8wiYZM56XqEJp5YlGTtqRjghV+JovWOqkWdR+amJpV31KPWQUvn1Q== + dependencies: + workbox-core "6.4.2" + +workbox-streams@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.4.2.tgz#3bc615cccebfd62dedf28315afb7d9ee177912a5" + integrity sha512-ROEGlZHGVEgpa5bOZefiJEVsi5PsFjJG9Xd+wnDbApsCO9xq9rYFopF+IRq9tChyYzhBnyk2hJxbQVWphz3sog== + dependencies: + workbox-core "6.4.2" + workbox-routing "6.4.2" + +workbox-sw@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.4.2.tgz#9a6db5f74580915dc2f0dbd47d2ffe057c94a795" + integrity sha512-A2qdu9TLktfIM5NE/8+yYwfWu+JgDaCkbo5ikrky2c7r9v2X6DcJ+zSLphNHHLwM/0eVk5XVf1mC5HGhYpMhhg== + +workbox-webpack-plugin@^6.4.1: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.4.2.tgz#aad9f11b028786d5b781420e68f4e8f570ea9936" + integrity sha512-CiEwM6kaJRkx1cP5xHksn13abTzUqMHiMMlp5Eh/v4wRcedgDTyv6Uo8+Hg9MurRbHDosO5suaPyF9uwVr4/CQ== + dependencies: + fast-json-stable-stringify "^2.1.0" + pretty-bytes "^5.4.1" + source-map-url "^0.4.0" + upath "^1.2.0" + webpack-sources "^1.4.3" + workbox-build "6.4.2" + +workbox-window@6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.4.2.tgz#5319a3e343fa1e4bd15a1f53a07b58999d064c8a" + integrity sha512-KVyRKmrJg7iB+uym/B/CnEUEFG9CvnTU1Bq5xpXHbtgD9l+ShDekSl1wYpqw/O0JfeeQVOFb8CiNfvnwWwqnWQ== + dependencies: + "@types/trusted-types" "^2.0.2" + workbox-core "6.4.2" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@^7.4.6: + version "7.5.7" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" + integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== + +ws@^8.4.2: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + +ws@~8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" + integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + +xtend@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yeast@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" + integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/extensions/yaml_support/README.md b/extensions/yaml_support/README.md index 0eeb1b5d7..1aae31ba1 100644 --- a/extensions/yaml_support/README.md +++ b/extensions/yaml_support/README.md @@ -1,20 +1,21 @@ -# YAML Support for LabGraph +# YAML Support | LabGraph Monitor -This extension provides an API to generate a serialized version of the labgraph topology. The serialized graph topology can be used in different applications E.g: server-client communication or to get a simplified overview of the topology in case of complicated graphs. +**This extension provides an API to generate a serialized version of the graph topology.** + +The serialized graph topology can be used in different applications. But it was specifically designed to work with server-client communications, such as working in sync with [LabGraph Monitor Front-End](https://github.com/facebookresearch/labgraph/tree/main/extensions/prototypes/labgraph_monitor) to get a simplified overview of the topology, along with real-time messaging, in case of complicated graphs. -## Quick Start -### Method 1 - building from source code +## Quick Start -**Prerequisites**: +### Prerequisites: - Python3\ Supported python version(s) - _ [Python3.6](https://www.python.org/downloads/) - _ [Python3.8](https://www.python.org/downloads/) (**RECOMMENDED**) -- Make sure to install [labgraph](https://github.com/facebookresearch/labgraph) before proceeding + - [Python3.6](https://www.python.org/downloads/) + - [Python3.8](https://www.python.org/downloads/) (**RECOMMENDED**) +- [LabGraph](https://github.com/facebookresearch/labgraph) -``` +```bash cd labgraph/extensions/yaml_support python setup.py install ``` @@ -25,43 +26,52 @@ To make sure things are working: 1- Move to the root of the LabGraph directory: -``` +```bash labgraph\extensions\yaml_support> cd ../.. labgraph> ``` 2- Run the following tests -``` +```bash python -m extensions.yaml_support.labgraph_monitor.tests.test_lg_monitor_api -``` -``` python -m extensions.yaml_support.labgraph_yaml_parser.tests.test_lg_yaml_api ``` -### API +### LabGraph Monitor API: -#### Labgraph Monitor: +#### Stream Graph Topology Only: -**generate_labgraph_monitor(graph: lg.Graph) -> None** : This function can be used to generate a serialized version of the passed graph instance. The serialized version of the graph will be streamed -to the clients using LabGraph Websockets API. + - **`LabgraphMonitor(graph: lg.Graph)`** : This class serves as a facade for monitor's functions, + such as sending either topology or topology + real-time messages + - **`stream_graph_topology() -> None`**: This function is used to send the generated serialized version of the graph instance via LabGraph WebSockets API -1. Call **generate_labgraph_monitor(graph: lg.Graph) -> None** and pass an instance of the graph as a parameter +The serialized version of the graph will be streamed to the clients using LabGraph's [WebSockets API](https://github.com/facebookresearch/labgraph/blob/main/docs/websockets-api.md). -``` -from extensions.yaml_support.labgraph_monitor.generate_lg_monitor.generate_lg_monitor import ( - generate_labgraph_monitor -) +1. Instantiate a monitor object with **`LabgraphMonitor(graph: lg.Graph)`** to serialize your graph and stream via WebSockets API using its method **`stream_graph_topology() -> None`** + +```python +from extensions.graphviz_support.graphviz_support.tests.demo_graph.demo import Demo + +from extensions.yaml_support.labgraph_monitor.labgraph_monitor import LabgraphMonitor -generate_labgraph_monitor(graph) + +# Initialize a Demo graph +graph = Demo() + +# Initialize a monitor object +monitor = LabgraphMonitor(graph) + +# Run the WebSockets API to send the topology to Front-End +monitor.stream_graph_topology() ``` This will start a websocket server on localhost port 9000 (127.0.0.1:9000) 2. To start receiving data from the server, send the following `StartStreamRequest` to **ws://127.0.0.1:9000** -``` +```bash { "api_version": "0.1", "api_request": { @@ -79,7 +89,7 @@ A serialized representation of the graph should be received each 200ms The graph representation has the following schema: -``` +```python { name: "graph_name", nodes: { @@ -88,7 +98,14 @@ The graph representation has the following schema: "upstream_name":[ { name: "message_name", - type: "message_type", + fields: { + "timestamp": { + "type": "float", + }, + "data": { + "type": "ndarray" + } + } } ] } @@ -97,9 +114,9 @@ The graph representation has the following schema: } ``` -E.g: +An example of a single WebSockets API message -``` +```python { "stream_batch": { "stream_id": "LABGRAPH.MONITOR", @@ -118,8 +135,12 @@ E.g: { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type": "float" + }, + "data": { + "type": "ndarray" + } } } ] @@ -131,8 +152,12 @@ E.g: { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type": "float" + }, + "data": { + "type": "ndarray" + } } } ] @@ -144,8 +169,12 @@ E.g: { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type": "float" + }, + "data": { + "type": "ndarray" + } } } ] @@ -157,8 +186,12 @@ E.g: { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type": "float" + }, + "data": { + "type": "ndarray" + } } } ], @@ -166,8 +199,12 @@ E.g: { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type": "float" + }, + "data": { + "type": "ndarray" + } } } ], @@ -175,8 +212,12 @@ E.g: { "name": "RandomMessage", "fields": { - "timestamp": "float", - "data": "ndarray" + "timestamp": { + "type": "float" + }, + "data": { + "type": "ndarray" + } } } ] @@ -184,22 +225,1029 @@ E.g: } } }, - "produced_timestamp_s": 1644931422.141309, - "timestamp_s": 1644931422.141309 + "produced_timestamp_s": 1648581339.9652574, + "timestamp_s": 1648581339.965258 } ], - "batch_num": 54 + "batch_num": 31 } } } ``` The above-serialized representation was generated for the following graph: -https://github.com/facebookresearch/labgraph/blob/main/extensions/graphviz_support/graphviz_support/tests/demo_graph/demo.py +[`extensions/graphviz_support/graphviz_support/tests/demo_graph/demo.py`](https://github.com/facebookresearch/labgraph/blob/main/extensions/graphviz_support/graphviz_support/tests/demo_graph/demo.py +) 3. To stop receiving data from the server, send the following `EndStreamRequest` to **ws://127.0.0.1:9000** +```python +{ + "api_version": "0.1", + "api_request": { + "request_id": 1, + "end_stream_request": { + "stream_id": "LABGRAPH.MONITOR", + "labgraph.monitor": { + } + } + } +} +``` + +#### Stream Graph Topology AND Real-Time Messages: + +This is used to stream both the graph topology and real-time messages received by nodes. However, it requires certain modifications to your graph outlined below: + +1. Make sure that your graph is compatible with real-time messaging + + - It requires a `set_topology()` method to add attributes to your graph. This function is used by `generate_graph_topology(graph: lg.Graph) -> None` internally + + ```python + def set_topology(self, topology: SerializedGraph, sub_pub_map: Dict[str, str]) -> None: + self._topology = topology + self._sub_pub_match = sub_pub_map + ``` + +- It also requires adding `WS_SERVER_NODE` and `SERIALIZER` nodes to your graph + + ```python + self.WS_SERVER_NODE.configure( + WSAPIServerConfig( + app_id=APP_ID, + ip=WS_SERVER.DEFAULT_IP, + port=ENUMS.WS_SERVER.DEFAULT_PORT, + api_version=ENUMS.WS_SERVER.DEFAULT_API_VERSION, + num_messages=-1, + enums=ENUMS(), + sample_rate=SAMPLE_RATE, + ) + ) + self.SERIALIZER.configure( + SerializerConfig( + data=self._topology, + sub_pub_match=self._sub_pub_match, + sample_rate=SAMPLE_RATE, + stream_name=STREAM.LABGRAPH_MONITOR, + stream_id=STREAM.LABGRAPH_MONITOR_ID, + ) + ) + ``` + + - As well as establishing connections between those nodes. Learn more in the example at [`yaml_support/labgraph_monitor/examples/labgraph_monitor_example.py`](https://github.com/facebookresearch/labgraph/blob/main/extensions/yaml_support/labgraph_monitor/examples/labgraph_monitor_example.py) + + 2. You can then run your graph + + ```python + from extensions.yaml_support.labgraph_monitor.examples.labgraph_monitor_example import Demo + + from extensions.yaml_support.labgraph_monitor.labgraph_monitor import LabgraphMonitor + + # Initialize a Demo graph + graph = Demo() + + # Initialize a monitor object + monitor = LabgraphMonitor(graph) + + # Run the WebSockets API to send the real-time topology to Front-End + monitor.stream_real_time_graph() + ``` + +3. Alternatively, you can simply run the example graph to try it out + + ```bash + labgraph> python extensions/yaml_support/labgraph_monitor/examples/labgraph_monitor_example.py + ``` + +4. To start receiving data from the server, send the following `StartStreamRequest` to **ws://127.0.0.1:9000** + + ```bash + { + "api_version": "0.1", + "api_request": { + "request_id": 1, + "start_stream_request": { + "stream_id": "LABGRAPH.MONITOR", + "labgraph.monitor": { + } + } + } + } + ``` + +The graph representation has the following schema: + +```python +{ + name: "graph_name", + nodes: { + "node_name":{ + upstreams:{ + "upstream_name":[ + { + name: "message_name", + fields: { + "timestamp": { + "type": "float", + "content": real-time value + }, + "data": { + "type": "ndarray", + "content" real-time value + } + } + } + ] + } + } + } +} +``` + + +
+ +Click to see a full example of a serialized graph + +```python + +{ + "stream_batch": { + "stream_id": "LABGRAPH.MONITOR", + "labgraph.monitor": { + "samples": [ + { + "data": { + "nodes": { + "WSAPIServerNode": { + "upstreams": { + "Serializer": [ + { + "name": "WSStreamMessage", + "fields": { + "samples": { + "type": "float64" + }, + "stream_name": { + "type": "str" + }, + "stream_id": { + "type": "str" + } + } + } + ] + } + }, + "Serializer": { + "upstreams": { + "NoiseGenerator": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": { + "type": "float", + "content": 1649542864.7681 + }, + "data": { + "type": "ndarray", + "content": [ + 0.6556638018597015, + 0.24539091264180568, + 0.5181571344277014, + 0.15984381323928065, + 0.3701566776459857, + 0.7696829688461163, + 0.5492927884621397, + 0.21755380404615476, + 0.175755558441702, + 0.3505148522415169, + 0.4350243443086518, + 0.8366274099373696, + 0.03302475842524499, + 0.8704738525112928, + 0.027029976451051985, + 0.204213678026263, + 0.27619466758117617, + 0.0025992584637967164, + 0.8518550799199274, + 0.9698040142464401, + 0.05805697223337192, + 0.6698569790361546, + 0.40671928409852365, + 0.5008805646909648, + 0.6510638383070676, + 0.5260106707400308, + 0.0582051587273289, + 0.25106713695105276, + 0.5142966826701221, + 0.6819891025463702, + 0.014206875156158705, + 0.2535009607475609, + 0.04284203715822765, + 0.44622787715227075, + 0.26505240918696915, + 0.6723324403079721, + 0.3993886748672555, + 0.8619632017716233, + 0.5675026279008025, + 0.4411726561239213, + 0.8971029120030483, + 0.13464404361226445, + 0.3257885658537706, + 0.09289484114827451, + 0.8086109528127211, + 0.23881475974032784, + 0.7182630487363211, + 0.6471818603995376, + 0.12258011209144437, + 0.18605697048575598, + 0.7339348679271822, + 0.3363211275559638, + 0.8530027602924856, + 0.40234748928650654, + 0.00039228723056528025, + 0.691446585785186, + 0.8633929722854425, + 0.4511881940645861, + 0.48228544914544236, + 0.9744417858895236, + 0.18154825917557527, + 0.9753096692304941, + 0.2717803735585991, + 0.1053234497045098, + 0.7827688514997935, + 0.735434301027755, + 0.7930935798860846, + 0.9266795158135795, + 0.7039903194866428, + 0.11595408071998414, + 0.7800548036145231, + 0.5897624086470854, + 0.2678583417730337, + 0.3096243301685998, + 0.21754492739103604, + 0.37433310319419066, + 0.008940695692322698, + 0.5934725005680236, + 0.024659685233431206, + 0.006249709051017738, + 0.5939358352718239, + 0.9032715460908528, + 0.5828267649749131, + 0.4146803854885678, + 0.8200852939412642, + 0.7025396722944489, + 0.356302414976882, + 0.5966468416890455, + 0.27785808269475387, + 0.5186198788914278, + 0.8821182046756642, + 0.025102814974933163, + 0.861080010979159, + 0.8681611199938043, + 0.483135709281621, + 0.05159925273309407, + 0.6374936766144756, + 0.25726267728691266, + 0.4150461824153807, + 0.4038900091612285 + ] + } + } + } + ], + "RollingAverager": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": { + "type": "float", + "content": 1649542864.778323 + }, + "data": { + "type": "ndarray", + "content": [ + 0.4808310849498126, + 0.45765738986698584, + 0.5179472617869983, + 0.4545298437611365, + 0.511475834694006, + 0.6370143504941551, + 0.5240116417975883, + 0.6621674003277452, + 0.39416145544465786, + 0.626802455660307, + 0.4379461617134113, + 0.626555296204266, + 0.5212543973073265, + 0.5634172170782334, + 0.6610150120658533, + 0.5648671072511644, + 0.36228861954728664, + 0.40611552677263923, + 0.528085032194146, + 0.3795449315341961, + 0.5564513831254658, + 0.6020673565819801, + 0.3920276747808733, + 0.5398939864307285, + 0.44071762995863917, + 0.4685674882997472, + 0.523320469875816, + 0.4682660890981684, + 0.4880738071953582, + 0.6247477549472256, + 0.37262642294916287, + 0.5292429080330315, + 0.3560642133686165, + 0.42654180215294774, + 0.48254525230183826, + 0.474625466796003, + 0.566613001586366, + 0.6124545612880383, + 0.521925508913315, + 0.3644889004429638, + 0.5516832937307365, + 0.5487269330238846, + 0.3776278084794831, + 0.45893729288107465, + 0.4358817754996432, + 0.5017929922223582, + 0.6077143546380854, + 0.43706695205815843, + 0.586934260076837, + 0.5603784617106875, + 0.5966846437692761, + 0.38536876311879753, + 0.5872066280396688, + 0.2818328200671419, + 0.5298252461922792, + 0.47645896345478744, + 0.6548108936817794, + 0.4247103938231261, + 0.5238473325787164, + 0.42887801140492804, + 0.5385221653828477, + 0.4554488020724207, + 0.5162366459311485, + 0.47389905275366334, + 0.4999364257892879, + 0.5837869623887693, + 0.5223744126545582, + 0.4912632015305095, + 0.45473824666589113, + 0.49088925869540045, + 0.5301869530654425, + 0.6173096417477979, + 0.4567178020980392, + 0.47735235791449043, + 0.4157221801958209, + 0.4549033636138871, + 0.46876329693604396, + 0.4322555231630121, + 0.5574453194471654, + 0.5911688810594924, + 0.5747328520895914, + 0.5189652304462694, + 0.5323873893686856, + 0.40735471004222557, + 0.5515387981684451, + 0.38342621200700266, + 0.438531088819206, + 0.515587963010385, + 0.32695685320343026, + 0.5585212227871539, + 0.6052757151735452, + 0.5496641665985119, + 0.5069727632398621, + 0.5296129345037791, + 0.5996831098341185, + 0.4823400590757597, + 0.44537321262358665, + 0.46411654667438745, + 0.5707572616456332, + 0.5388152812937068 + ] + } + } + } + ], + "Amplifier": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": { + "type": "float", + "content": 1649542864.7791843 + }, + "data": { + "type": "ndarray", + "content": [ + 0.7867965622316418, + 0.2944690951701668, + 0.6217885613132417, + 0.19181257588713677, + 0.4441880131751828, + 0.9236195626153395, + 0.6591513461545676, + 0.2610645648553857, + 0.21090667013004238, + 0.4206178226898203, + 0.5220292131703821, + 1.0039528919248435, + 0.03962971011029399, + 1.0445686230135514, + 0.03243597174126238, + 0.2450564136315156, + 0.3314336010974114, + 0.00311911015655606, + 1.022226095903913, + 1.1637648170957282, + 0.06966836668004629, + 0.8038283748433855, + 0.48806314091822833, + 0.6010566776291577, + 0.7812766059684811, + 0.631212804888037, + 0.06984619047279468, + 0.3012805643412633, + 0.6171560192041464, + 0.8183869230556443, + 0.017048250187390444, + 0.30420115289707306, + 0.051410444589873185, + 0.5354734525827248, + 0.31806289102436297, + 0.8067989283695666, + 0.47926640984070656, + 1.034355842125948, + 0.681003153480963, + 0.5294071873487055, + 1.076523494403658, + 0.16157285233471733, + 0.39094627902452467, + 0.1114738093779294, + 0.9703331433752653, + 0.2865777116883934, + 0.8619156584835853, + 0.7766182324794452, + 0.14709613450973325, + 0.22326836458290716, + 0.8807218415126187, + 0.40358535306715654, + 1.0236033123509827, + 0.48281698714380783, + 0.0004707446766783363, + 0.8297359029422232, + 1.036071566742531, + 0.5414258328775033, + 0.5787425389745308, + 1.1693301430674283, + 0.21785791101069032, + 1.170371603076593, + 0.3261364482703189, + 0.12638813964541176, + 0.9393226217997521, + 0.8825211612333059, + 0.9517122958633015, + 1.1120154189762954, + 0.8447883833839713, + 0.13914489686398096, + 0.9360657643374276, + 0.7077148903765025, + 0.32143001012764044, + 0.37154919620231974, + 0.26105391286924323, + 0.4491997238330288, + 0.010728834830787237, + 0.7121670006816283, + 0.029591622280117445, + 0.007499650861221285, + 0.7127230023261887, + 1.0839258553090234, + 0.6993921179698958, + 0.49761646258628134, + 0.984102352729517, + 0.8430476067533387, + 0.4275628979722584, + 0.7159762100268545, + 0.33342969923370464, + 0.6223438546697133, + 1.058541845610797, + 0.030123377969919794, + 1.0332960131749906, + 1.041793343992565, + 0.5797628511379451, + 0.06191910327971288, + 0.7649924119373708, + 0.3087152127442952, + 0.49805541889845684, + 0.48466801099347423 + ] + } + } + } + ], + "Attenuator": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": { + "type": "float", + "content": 1649542864.7761025 + }, + "data": { + "type": "ndarray", + "content": [ + 1.1659534387549473, + 0.43637360736158304, + 0.9214281633175516, + 0.28424696190551973, + 0.6582419983462712, + 1.3687113757566782, + 0.9767960558050885, + 0.3868714503109195, + 0.3125424907767712, + 0.6233133446539249, + 0.7735948343497585, + 1.4877572969658177, + 0.05872724792912261, + 1.54794572889809, + 0.048066850576742697, + 0.3631489788824215, + 0.49115129052215034, + 0.004622207807539101, + 1.5148363489586678, + 1.7245825103075385, + 0.10324151833180674, + 1.191192873490868, + 0.7232605285781757, + 0.8907055950786112, + 1.1577734182823687, + 0.9353939452377601, + 0.10350503532285629, + 0.44646752017747954, + 0.9145632014435997, + 1.2127671789291337, + 0.02526379357119053, + 0.4507955389224727, + 0.07618511256259868, + 0.7935178461252609, + 0.47133724183839865, + 1.19559493530089, + 0.7102246571191703, + 1.5328114139217033, + 1.0091782383389774, + 0.7845282506573513, + 1.5952996371009802, + 0.23943473044007263, + 0.5793430986838699, + 0.16519298331281304, + 1.437936208118817, + 0.42467937005961726, + 1.277272390559583, + 1.1508701768991823, + 0.21798168941247872, + 0.3308612797090394, + 1.3051412639445443, + 0.5980729362938445, + 1.5168772453344939, + 0.7154862558790507, + 0.0006975963049354294, + 1.2295852266435081, + 1.5353539453875087, + 0.8023386755576991, + 0.8576382839767142, + 1.7328297641288963, + 0.32284353122033543, + 1.73437310320446, + 0.4833014423519436, + 0.18729452200379984, + 1.3919817314418979, + 1.3078076749540237, + 1.4103419833454838, + 1.6478951026761268, + 1.25189149000982, + 0.20619875425433903, + 1.3871553959696619, + 1.0487623481120731, + 0.4763269739821545, + 0.5505985711859143, + 0.3868556651378918, + 0.6656688499062046, + 0.015899055061081146, + 1.0553599281844293, + 0.04385181050865158, + 0.011113728924158739, + 1.0561838667481538, + 1.6062691920873873, + 1.0364288357744824, + 0.7374175912613233, + 1.4583407926914678, + 1.2493118339767106, + 0.6336052483005331, + 1.0610047936403824, + 0.4941093073689731, + 0.9222510522695058, + 1.5686526405952337, + 0.04463981900394038, + 1.531240853920328, + 1.5438330442813126, + 0.8591502840700574, + 0.0917578887086558, + 1.1336418791535336, + 0.4574849219908048, + 0.7380680804045303, + 0.7182292872118445 + ] + } + } + } + ] + } + }, + "NoiseGenerator": { + "upstreams": {} + }, + "RollingAverager": { + "upstreams": { + "NoiseGenerator": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": { + "type": "float", + "content": 1649542864.7681 + }, + "data": { + "type": "ndarray", + "content": [ + 0.6556638018597015, + 0.24539091264180568, + 0.5181571344277014, + 0.15984381323928065, + 0.3701566776459857, + 0.7696829688461163, + 0.5492927884621397, + 0.21755380404615476, + 0.175755558441702, + 0.3505148522415169, + 0.4350243443086518, + 0.8366274099373696, + 0.03302475842524499, + 0.8704738525112928, + 0.027029976451051985, + 0.204213678026263, + 0.27619466758117617, + 0.0025992584637967164, + 0.8518550799199274, + 0.9698040142464401, + 0.05805697223337192, + 0.6698569790361546, + 0.40671928409852365, + 0.5008805646909648, + 0.6510638383070676, + 0.5260106707400308, + 0.0582051587273289, + 0.25106713695105276, + 0.5142966826701221, + 0.6819891025463702, + 0.014206875156158705, + 0.2535009607475609, + 0.04284203715822765, + 0.44622787715227075, + 0.26505240918696915, + 0.6723324403079721, + 0.3993886748672555, + 0.8619632017716233, + 0.5675026279008025, + 0.4411726561239213, + 0.8971029120030483, + 0.13464404361226445, + 0.3257885658537706, + 0.09289484114827451, + 0.8086109528127211, + 0.23881475974032784, + 0.7182630487363211, + 0.6471818603995376, + 0.12258011209144437, + 0.18605697048575598, + 0.7339348679271822, + 0.3363211275559638, + 0.8530027602924856, + 0.40234748928650654, + 0.00039228723056528025, + 0.691446585785186, + 0.8633929722854425, + 0.4511881940645861, + 0.48228544914544236, + 0.9744417858895236, + 0.18154825917557527, + 0.9753096692304941, + 0.2717803735585991, + 0.1053234497045098, + 0.7827688514997935, + 0.735434301027755, + 0.7930935798860846, + 0.9266795158135795, + 0.7039903194866428, + 0.11595408071998414, + 0.7800548036145231, + 0.5897624086470854, + 0.2678583417730337, + 0.3096243301685998, + 0.21754492739103604, + 0.37433310319419066, + 0.008940695692322698, + 0.5934725005680236, + 0.024659685233431206, + 0.006249709051017738, + 0.5939358352718239, + 0.9032715460908528, + 0.5828267649749131, + 0.4146803854885678, + 0.8200852939412642, + 0.7025396722944489, + 0.356302414976882, + 0.5966468416890455, + 0.27785808269475387, + 0.5186198788914278, + 0.8821182046756642, + 0.025102814974933163, + 0.861080010979159, + 0.8681611199938043, + 0.483135709281621, + 0.05159925273309407, + 0.6374936766144756, + 0.25726267728691266, + 0.4150461824153807, + 0.4038900091612285 + ] + } + } + } + ] + } + }, + "Amplifier": { + "upstreams": { + "NoiseGenerator": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": { + "type": "float", + "content": 1649542864.7681 + }, + "data": { + "type": "ndarray", + "content": [ + 0.6556638018597015, + 0.24539091264180568, + 0.5181571344277014, + 0.15984381323928065, + 0.3701566776459857, + 0.7696829688461163, + 0.5492927884621397, + 0.21755380404615476, + 0.175755558441702, + 0.3505148522415169, + 0.4350243443086518, + 0.8366274099373696, + 0.03302475842524499, + 0.8704738525112928, + 0.027029976451051985, + 0.204213678026263, + 0.27619466758117617, + 0.0025992584637967164, + 0.8518550799199274, + 0.9698040142464401, + 0.05805697223337192, + 0.6698569790361546, + 0.40671928409852365, + 0.5008805646909648, + 0.6510638383070676, + 0.5260106707400308, + 0.0582051587273289, + 0.25106713695105276, + 0.5142966826701221, + 0.6819891025463702, + 0.014206875156158705, + 0.2535009607475609, + 0.04284203715822765, + 0.44622787715227075, + 0.26505240918696915, + 0.6723324403079721, + 0.3993886748672555, + 0.8619632017716233, + 0.5675026279008025, + 0.4411726561239213, + 0.8971029120030483, + 0.13464404361226445, + 0.3257885658537706, + 0.09289484114827451, + 0.8086109528127211, + 0.23881475974032784, + 0.7182630487363211, + 0.6471818603995376, + 0.12258011209144437, + 0.18605697048575598, + 0.7339348679271822, + 0.3363211275559638, + 0.8530027602924856, + 0.40234748928650654, + 0.00039228723056528025, + 0.691446585785186, + 0.8633929722854425, + 0.4511881940645861, + 0.48228544914544236, + 0.9744417858895236, + 0.18154825917557527, + 0.9753096692304941, + 0.2717803735585991, + 0.1053234497045098, + 0.7827688514997935, + 0.735434301027755, + 0.7930935798860846, + 0.9266795158135795, + 0.7039903194866428, + 0.11595408071998414, + 0.7800548036145231, + 0.5897624086470854, + 0.2678583417730337, + 0.3096243301685998, + 0.21754492739103604, + 0.37433310319419066, + 0.008940695692322698, + 0.5934725005680236, + 0.024659685233431206, + 0.006249709051017738, + 0.5939358352718239, + 0.9032715460908528, + 0.5828267649749131, + 0.4146803854885678, + 0.8200852939412642, + 0.7025396722944489, + 0.356302414976882, + 0.5966468416890455, + 0.27785808269475387, + 0.5186198788914278, + 0.8821182046756642, + 0.025102814974933163, + 0.861080010979159, + 0.8681611199938043, + 0.483135709281621, + 0.05159925273309407, + 0.6374936766144756, + 0.25726267728691266, + 0.4150461824153807, + 0.4038900091612285 + ] + } + } + } + ] + } + }, + "Attenuator": { + "upstreams": { + "NoiseGenerator": [ + { + "name": "RandomMessage", + "fields": { + "timestamp": { + "type": "float", + "content": 1649542864.7681 + }, + "data": { + "type": "ndarray", + "content": [ + 0.6556638018597015, + 0.24539091264180568, + 0.5181571344277014, + 0.15984381323928065, + 0.3701566776459857, + 0.7696829688461163, + 0.5492927884621397, + 0.21755380404615476, + 0.175755558441702, + 0.3505148522415169, + 0.4350243443086518, + 0.8366274099373696, + 0.03302475842524499, + 0.8704738525112928, + 0.027029976451051985, + 0.204213678026263, + 0.27619466758117617, + 0.0025992584637967164, + 0.8518550799199274, + 0.9698040142464401, + 0.05805697223337192, + 0.6698569790361546, + 0.40671928409852365, + 0.5008805646909648, + 0.6510638383070676, + 0.5260106707400308, + 0.0582051587273289, + 0.25106713695105276, + 0.5142966826701221, + 0.6819891025463702, + 0.014206875156158705, + 0.2535009607475609, + 0.04284203715822765, + 0.44622787715227075, + 0.26505240918696915, + 0.6723324403079721, + 0.3993886748672555, + 0.8619632017716233, + 0.5675026279008025, + 0.4411726561239213, + 0.8971029120030483, + 0.13464404361226445, + 0.3257885658537706, + 0.09289484114827451, + 0.8086109528127211, + 0.23881475974032784, + 0.7182630487363211, + 0.6471818603995376, + 0.12258011209144437, + 0.18605697048575598, + 0.7339348679271822, + 0.3363211275559638, + 0.8530027602924856, + 0.40234748928650654, + 0.00039228723056528025, + 0.691446585785186, + 0.8633929722854425, + 0.4511881940645861, + 0.48228544914544236, + 0.9744417858895236, + 0.18154825917557527, + 0.9753096692304941, + 0.2717803735585991, + 0.1053234497045098, + 0.7827688514997935, + 0.735434301027755, + 0.7930935798860846, + 0.9266795158135795, + 0.7039903194866428, + 0.11595408071998414, + 0.7800548036145231, + 0.5897624086470854, + 0.2678583417730337, + 0.3096243301685998, + 0.21754492739103604, + 0.37433310319419066, + 0.008940695692322698, + 0.5934725005680236, + 0.024659685233431206, + 0.006249709051017738, + 0.5939358352718239, + 0.9032715460908528, + 0.5828267649749131, + 0.4146803854885678, + 0.8200852939412642, + 0.7025396722944489, + 0.356302414976882, + 0.5966468416890455, + 0.27785808269475387, + 0.5186198788914278, + 0.8821182046756642, + 0.025102814974933163, + 0.861080010979159, + 0.8681611199938043, + 0.483135709281621, + 0.05159925273309407, + 0.6374936766144756, + 0.25726267728691266, + 0.4150461824153807, + 0.4038900091612285 + ] + } + } + } + ] + } + } + } + }, + "produced_timestamp_s": 1649542864.9706066, + "timestamp_s": 1649542864.9706097 + } + ], + "batch_num": 53 + } + } +} + ``` + +
+ +3. To stop receiving data from the server, send the following `EndStreamRequest` to **ws://127.0.0.1:9000** + +```python { "api_version": "0.1", "api_request": { @@ -213,13 +1261,13 @@ https://github.com/facebookresearch/labgraph/blob/main/extensions/graphviz_suppo } ``` -#### Labgraph YAML Parser: +# LabGraph YAML Parser: -**yamlify(python_file: str, yaml_file: str) -> str** : This function can be used to generate a YAMLIFIED version of the passed LabGraph source code(.py). The serialized version will be saved as a YAML file at the specified folder(yml_file) +**`yamlify(python_file: str, yaml_file: str) -> str`** : This function can be used to generate a YAMLIFIED version of the passed LabGraph source code (.py). The serialized version will be saved as a YAML file at the specified folder (yml_file) 1. Call **yamlify(python_file: str, yaml_file: str) -> str** and pass the appropriate parameters -``` +```python from extensions.yaml_support.labgraph_yaml_parser.yamlify import ( yamlify ) @@ -228,3 +1276,88 @@ yamlify(python_file, output_yaml_file) ``` This will generate a YAML file in the specified location + +Example of a YAML file produced for [`simple_viz.py`](https://github.com/facebookresearch/labgraph/blob/main/labgraph/examples/simple_viz.py) example: + +```python +RandomMessage: + type: Message + fields: + timestamp: float + data: np.ndarray + +NoiseGeneratorConfig: + type: Config + fields: + sample_rate: float + num_features: int + +NoiseGenerator: + type: Node + config: NoiseGeneratorConfig + inputs: [] + outputs: + - RandomMessage + +RollingState: + type: State + fields: + messages: List.RandomMessage + +RollingConfig: + type: Config + fields: + window: float + +RollingAverager: + type: Node + state: RollingState + config: RollingConfig + inputs: + - RandomMessage + outputs: + - RandomMessage + +AveragedNoiseConfig: + type: Config + fields: + sample_rate: float + num_features: int + window: float + +AveragedNoise: + type: Group + config: AveragedNoiseConfig + inputs: [] + outputs: + - RandomMessage + connections: + NoiseGenerator: RollingAverager + RollingAverager: AveragedNoise + +PlotState: + type: State + fields: + data: Optional.np.ndarray + +PlotConfig: + type: Config + fields: + refresh_rate: float + num_bars: int + +Plot: + type: Node + state: PlotState + config: PlotConfig + inputs: + - RandomMessage + outputs: [] + +Demo: + type: Graph + inputs: [] + outputs: [] + connections: + AveragedNoise: Plot +``` diff --git a/extensions/yaml_support/labgraph_monitor/examples/labgraph_monitor_example.py b/extensions/yaml_support/labgraph_monitor/examples/labgraph_monitor_example.py new file mode 100644 index 000000000..59bcc7247 --- /dev/null +++ b/extensions/yaml_support/labgraph_monitor/examples/labgraph_monitor_example.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import labgraph as lg +from typing import Dict, Tuple + +# Make the imports work when running from LabGraph root directory +import sys +sys.path.append("./") + +# LabGraph WebSockets Components +from labgraph.websockets.ws_server.ws_api_node_server import ( + WSAPIServerConfig, + WSAPIServerNode, +) + +# LabGraph Monitor Components +from extensions.yaml_support.labgraph_monitor.server.enums.enums import ENUMS +from extensions.yaml_support.labgraph_monitor.aliases.aliases import SerializedGraph +from extensions.yaml_support.labgraph_monitor.server.serializer_node import SerializerConfig, Serializer +from extensions.yaml_support.labgraph_monitor.generate_lg_monitor.generate_lg_monitor import set_graph_topology + +# Graph Components +from extensions.graphviz_support.graphviz_support.tests.demo_graph.noise_generator import NoiseGeneratorConfig, NoiseGenerator +from extensions.graphviz_support.graphviz_support.tests.demo_graph.amplifier import AmplifierConfig, Amplifier +from extensions.graphviz_support.graphviz_support.tests.demo_graph.attenuator import AttenuatorConfig, Attenuator +from extensions.graphviz_support.graphviz_support.tests.demo_graph.rolling_averager import RollingConfig, RollingAverager + +# LabGraph WebSockets Configurations +APP_ID = 'LABGRAPH.MONITOR' +WS_SERVER = ENUMS.WS_SERVER +STREAM = ENUMS.STREAM +DEFAULT_IP = WS_SERVER.DEFAULT_IP +DEFAULT_PORT = WS_SERVER.DEFAULT_PORT +DEFAULT_API_VERSION = WS_SERVER.DEFAULT_API_VERSION +SAMPLE_RATE = 5 + +# Graph Configurations +NUM_FEATURES = 100 +WINDOW = 2.0 +REFRESH_RATE = 2.0 +OUT_IN_RATIO = 1.2 +ATTENUATION = 5.0 + +class Demo(lg.Graph): + # LabGraph WebSockets Component + WS_SERVER_NODE: WSAPIServerNode + + # LabGraph Monitor Component + SERIALIZER: Serializer + + # Graph Components + NOISE_GENERATOR: NoiseGenerator + ROLLING_AVERAGER: RollingAverager + AMPLIFIER: Amplifier + ATTENUATOR: Attenuator + + # Used when running `generate_labgraph_monitor(graph)` + def set_topology(self, topology: SerializedGraph, sub_pub_map: Dict) -> None: + self._topology = topology + self._sub_pub_match = sub_pub_map + + def setup(self) -> None: + self.WS_SERVER_NODE.configure( + WSAPIServerConfig( + app_id=APP_ID, + ip=WS_SERVER.DEFAULT_IP, + port=ENUMS.WS_SERVER.DEFAULT_PORT, + api_version=ENUMS.WS_SERVER.DEFAULT_API_VERSION, + num_messages=-1, + enums=ENUMS(), + sample_rate=SAMPLE_RATE, + ) + ) + self.SERIALIZER.configure( + SerializerConfig( + data=self._topology, + sub_pub_match=self._sub_pub_match, + sample_rate=SAMPLE_RATE, + stream_name=STREAM.LABGRAPH_MONITOR, + stream_id=STREAM.LABGRAPH_MONITOR_ID, + ) + ) + self.NOISE_GENERATOR.configure( + NoiseGeneratorConfig( + sample_rate=float(SAMPLE_RATE), + num_features=NUM_FEATURES, + ) + ) + self.ROLLING_AVERAGER.configure( + RollingConfig( + window=WINDOW, + ) + ) + self.AMPLIFIER.configure( + AmplifierConfig( + out_in_ratio=OUT_IN_RATIO, + ) + ) + self.ATTENUATOR.configure( + AttenuatorConfig( + attenuation=ATTENUATION, + ) + ) + + def connections(self) -> lg.Connections: + return ( + (self.NOISE_GENERATOR.NOISE_GENERATOR_OUTPUT, self.ROLLING_AVERAGER.ROLLING_AVERAGER_INPUT), + (self.NOISE_GENERATOR.NOISE_GENERATOR_OUTPUT, self.AMPLIFIER.AMPLIFIER_INPUT), + (self.NOISE_GENERATOR.NOISE_GENERATOR_OUTPUT, self.ATTENUATOR.ATTENUATOR_INPUT), + (self.NOISE_GENERATOR.NOISE_GENERATOR_OUTPUT, self.SERIALIZER.SERIALIZER_INPUT_1), + (self.ROLLING_AVERAGER.ROLLING_AVERAGER_OUTPUT, self.SERIALIZER.SERIALIZER_INPUT_2), + (self.AMPLIFIER.AMPLIFIER_OUTPUT, self.SERIALIZER.SERIALIZER_INPUT_3), + (self.ATTENUATOR.ATTENUATOR_OUTPUT, self.SERIALIZER.SERIALIZER_INPUT_4), + (self.SERIALIZER.SERIALIZER_OUTPUT, self.WS_SERVER_NODE.topic), + ) + + def process_modules(self) -> Tuple[lg.Module, ...]: + return ( + self.NOISE_GENERATOR, + self.ROLLING_AVERAGER, + self.AMPLIFIER, + self.ATTENUATOR, + self.SERIALIZER, + self.WS_SERVER_NODE, + ) + +if __name__ == "__main__": + graph = Demo() + set_graph_topology(graph=graph) + + runner = lg.ParallelRunner(graph=graph) + runner.run() \ No newline at end of file diff --git a/extensions/yaml_support/labgraph_monitor/generate_lg_monitor/generate_lg_monitor.py b/extensions/yaml_support/labgraph_monitor/generate_lg_monitor/generate_lg_monitor.py index 7f67f1152..7f50c1835 100644 --- a/extensions/yaml_support/labgraph_monitor/generate_lg_monitor/generate_lg_monitor.py +++ b/extensions/yaml_support/labgraph_monitor/generate_lg_monitor/generate_lg_monitor.py @@ -1,14 +1,12 @@ #!/usr/bin/env python3 -# Copyright 2004-present Facebook. All Rights Reserve +# Copyright 2004-present Facebook. All Rights Reserved. import labgraph as lg from labgraph.graphs.stream import Stream -from typing import List, Dict +from typing import List, Dict, Tuple from ..lg_monitor_node.lg_monitor_node import LabgraphMonitorNode from ..lg_monitor_node.lg_monitor_message import LabgraphMonitorMessage from ..aliases.aliases import SerializedGraph -from ..server.lg_monitor_server import run_server - def identify_upstream_message( in_edge: str, @@ -141,10 +139,7 @@ def connect_to_upstream( return nodes -def serialize_graph( - name: str, - nodes: List[LabgraphMonitorNode] -) -> SerializedGraph: +def serialize_graph(graph: lg.Graph) -> SerializedGraph: """ A function that returns a serialized version of the graph topology. @@ -154,8 +149,17 @@ def serialize_graph( @return: A serialized version of the graph topology """ + # List of graph nodes + nodes: List[LabgraphMonitorNode] = [] + + # Identify graph nodes + nodes = identify_graph_nodes(graph) + + # Connect graph edges + nodes = connect_to_upstream(nodes, graph.__streams__.values()) + serialized_graph: SerializedGraph = { - "name": name, + "name": type(graph).__name__, "nodes": {} } @@ -185,31 +189,91 @@ def serialize_graph( return serialized_graph - -def generate_labgraph_monitor(graph: lg.Graph) -> None: +def sub_pub_grouping_map(graph: lg.Graph) -> Dict[str, str]: """ - A function that serialize the graph topology - and send it using to LabGraphMonitor Front-End - using Labgraph Websocket API + A function that matches subscribers with their publishers + to automate assigning real-time messages to serialized graph @params: graph: An instance of the computational graph + + @return: A dictionary where the key is a publisher grouping + and the value is a dictionary of sets of topic paths subscribed and their groupings """ - # Local variables - nodes: List[LabgraphMonitorNode] = [] + sub_pub_grouping_map: Dict[str, str] = {} + for stream in graph.__streams__.values(): + difference = set(stream.topic_paths).difference(LabgraphMonitorNode.in_edges) + if difference: + upstream_edge = max(difference, key=len) + for edge in stream.topic_paths: + if edge != upstream_edge: + # convert SERIALIZER/SERIALIZER_INPUT_1 to its grouping Serializer + edge_path = "/".join(edge.split("/")[:-1]) + edge_grouping = type(graph.__descendants__[edge_path]).__name__ + + # convert SERIALIZER/SERIALIZER_INPUT_1 to its topic SERIALIZER_INPUT_1 + topic_path = edge.split("/")[-1] + + # convert NOISE_GENERATOR/NOISE_GENERATOR_OUTPUT to its grouping NoiseGenerator + group_path = "/".join(upstream_edge.split("/")[:-1]) + grouping = type(graph.__descendants__[group_path]).__name__ + + if grouping in sub_pub_grouping_map: + sub_pub_grouping_map[grouping]["topics"].add(topic_path) + sub_pub_grouping_map[grouping]["subscribers"].add(edge_grouping) + else: + sub_pub_grouping_map[grouping] = { + "topics": {topic_path}, + "subscribers": {edge_grouping}, + } + + return sub_pub_grouping_map + +def generate_graph_topology(graph: lg.Graph) -> SerializedGraph: + """ + A function that serializes the graph topology + and sends it to LabGraph Monitor Front-End + using WebSockets API - # Identify graph nodes - nodes = identify_graph_nodes(graph) + @params: + graph: An instance of the computational graph + + @return: Serialized topology of the graph + """ + serialized_graph = serialize_graph(graph) - # Connect graph edges - nodes = connect_to_upstream(nodes, graph.__streams__.values()) + return serialized_graph +def set_graph_topology(graph: lg.Graph) -> None: + """ + A function that serializes the graph topology + and applies the information to serve as graph + attribute for LabGraph Monitor Front-End + real-time messaging using WebSockets API + + @params: + graph: An instance of the computational graph + """ # Serialize the graph topology - serialized_graph = serialize_graph( - type(graph).__name__, - nodes - ) - - # Send the serialized graph to Front-End - # using LabGraph Websockets API - run_server(serialized_graph) + serialized_graph = serialize_graph(graph) + + # Match subscribers with their publishers + sub_pub_map = sub_pub_grouping_map(graph) + + # Set graph's topology and real-time messages matching + if hasattr(graph, "set_topology"): + graph.set_topology(serialized_graph, sub_pub_map) + else: + raise AttributeError( + """ + Provided graph is missing `set_topology` method to establish + its topology and possible real-time messsaging + + Please add the following method to your graph + ``` + def set_topology(self, topology: SerializedGraph, sub_pub_map: Dict) -> None: + self._topology = topology + self._sub_pub_match = sub_pub_map + ``` + """ + ) \ No newline at end of file diff --git a/extensions/yaml_support/labgraph_monitor/labgraph_monitor.py b/extensions/yaml_support/labgraph_monitor/labgraph_monitor.py new file mode 100644 index 000000000..2c6078926 --- /dev/null +++ b/extensions/yaml_support/labgraph_monitor/labgraph_monitor.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved, + +import labgraph as lg +from .generate_lg_monitor.generate_lg_monitor import generate_graph_topology, set_graph_topology +from .server.lg_monitor_server import run_topology + +class LabgraphMonitor: + """ + A class that serves as a facade for + LabGraph Monitor's Back-End functions + """ + + def __init__(self, graph: lg.Graph) -> None: + self.graph = graph + + def stream_graph_topology(self) -> None: + """ + Stream graph topology via WebSockets + """ + topology = generate_graph_topology(self.graph) + run_topology(topology) + + def stream_real_time_graph(self) -> None: + """ + Stream graph topology and real-time + messages via WebSockets + """ + set_graph_topology(self.graph) + + runner = lg.ParallelRunner(self.graph) + runner.run() \ No newline at end of file diff --git a/extensions/yaml_support/labgraph_monitor/lg_monitor_node/lg_monitor_message.py b/extensions/yaml_support/labgraph_monitor/lg_monitor_node/lg_monitor_message.py index 1e4916762..c4024647f 100644 --- a/extensions/yaml_support/labgraph_monitor/lg_monitor_node/lg_monitor_message.py +++ b/extensions/yaml_support/labgraph_monitor/lg_monitor_node/lg_monitor_message.py @@ -34,6 +34,6 @@ def serialize(self) -> SerializedMessage: name = annotation[0] type = (annotation[1]).__name__ - serialized_message['fields'][name] = type + serialized_message['fields'][name] = {"type": type} return serialized_message diff --git a/extensions/yaml_support/labgraph_monitor/server/enums/enums.py b/extensions/yaml_support/labgraph_monitor/server/enums/enums.py index 2a39b0966..832756013 100644 --- a/extensions/yaml_support/labgraph_monitor/server/enums/enums.py +++ b/extensions/yaml_support/labgraph_monitor/server/enums/enums.py @@ -2,9 +2,15 @@ # Copyright 2004-present Facebook. All Rights Reserved. from .enums_base import ENUMS as ENUMS_base +class STREAM_LISTS: + supported_stream_list = [ + "labgraph.monitor", + ] + sleep_pause_streams = supported_stream_list class ENUMS: API = ENUMS_base.API WS_SERVER = ENUMS_base.WS_SERVER STREAM = ENUMS_base.STREAM + STREAM_LISTS = STREAM_LISTS \ No newline at end of file diff --git a/extensions/yaml_support/labgraph_monitor/server/lg_monitor_server.py b/extensions/yaml_support/labgraph_monitor/server/lg_monitor_server.py index 552226f14..1f5053b63 100644 --- a/extensions/yaml_support/labgraph_monitor/server/lg_monitor_server.py +++ b/extensions/yaml_support/labgraph_monitor/server/lg_monitor_server.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright 2004-present Facebook. All Rights Reserve +# Copyright 2004-present Facebook. All Rights Reserved. import labgraph as lg from typing import Tuple @@ -20,7 +20,7 @@ SAMPLE_RATE = 5 -def run_server(data: SerializedGraph) -> None: +def run_topology(data: SerializedGraph) -> None: """ A function that creates a Websocket server graph. The server graph streams the lagraph topology to the clients @@ -50,7 +50,7 @@ def setup(self) -> None: self.WS_SERVER_NODE.configure(wsapi_server_config) def connections(self) -> lg.Connections: - return ((self.SERIALIZER.TOPIC, self.WS_SERVER_NODE.topic),) + return ((self.SERIALIZER.SERIALIZER_OUTPUT, self.WS_SERVER_NODE.topic),) def process_modules(self) -> Tuple[lg.Module, ...]: return (self.SERIALIZER, self.WS_SERVER_NODE, ) diff --git a/extensions/yaml_support/labgraph_monitor/server/serializer_node.py b/extensions/yaml_support/labgraph_monitor/server/serializer_node.py index 064294ab7..5395bd3e1 100644 --- a/extensions/yaml_support/labgraph_monitor/server/serializer_node.py +++ b/extensions/yaml_support/labgraph_monitor/server/serializer_node.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 -# Copyright 2004-present Facebook. All Rights Reserve +# Copyright 2004-present Facebook. All Rights Reserved. +from dataclasses import field +from typing import Dict, Optional import labgraph as lg import asyncio from labgraph.websockets.ws_server.ws_server_stream_message import ( @@ -8,30 +10,111 @@ ) from ..aliases.aliases import SerializedGraph +# Make it work with RandomMessage +from ....graphviz_support.graphviz_support.tests.demo_graph.random_message import RandomMessage class SerializerConfig(lg.Config): data: SerializedGraph + sub_pub_match: Optional[Dict] = field(default_factory=dict) sample_rate: int stream_name: str stream_id: str +class DataState(lg.State): + data_1: Optional[Dict] = None + data_2: Optional[Dict] = None + data_3: Optional[Dict] = None + data_4: Optional[Dict] = None class Serializer(lg.Node): """ Convenience node for sending messages to a `WSAPIServerNode`. """ - TOPIC = lg.Topic(WSStreamMessage) + SERIALIZER_OUTPUT = lg.Topic(WSStreamMessage) config: SerializerConfig + state: DataState - @lg.publisher(TOPIC) + SERIALIZER_INPUT_1 = lg.Topic(RandomMessage) + SERIALIZER_INPUT_2 = lg.Topic(RandomMessage) + SERIALIZER_INPUT_3 = lg.Topic(RandomMessage) + SERIALIZER_INPUT_4 = lg.Topic(RandomMessage) + + def get_grouping(self, topic: lg.Topic) -> str: + """ + Matches subscriber topics with grouping that produced information + """ + for key, value in self.config.sub_pub_match.items(): + if topic.name in value["topics"]: + return key + return "" + + @lg.subscriber(SERIALIZER_INPUT_1) + def add_message_1(self, message: RandomMessage) -> None: + grouping = self.get_grouping(self.SERIALIZER_INPUT_1) + self.state.data_1 = { + "grouping": grouping, + "timestamp": message.timestamp, + "numpy": list(message.data), + } + + @lg.subscriber(SERIALIZER_INPUT_2) + def add_message_2(self, message: RandomMessage) -> None: + grouping = self.get_grouping(self.SERIALIZER_INPUT_2) + self.state.data_2 = { + "grouping": grouping, + "timestamp": message.timestamp, + "numpy": list(message.data), + } + + @lg.subscriber(SERIALIZER_INPUT_3) + def add_message_3(self, message: RandomMessage) -> None: + grouping = self.get_grouping(self.SERIALIZER_INPUT_3) + self.state.data_3 = { + "grouping": grouping, + "timestamp": message.timestamp, + "numpy": list(message.data), + } + + @lg.subscriber(SERIALIZER_INPUT_4) + def add_message_4(self, message: RandomMessage) -> None: + grouping = self.get_grouping(self.SERIALIZER_INPUT_4) + self.state.data_4 = { + "grouping": grouping, + "timestamp": message.timestamp, + "numpy": list(message.data), + } + + def output(self, _in: Dict) -> Dict: + """ + Updates serialized message with data according to grouping + + @params: + value of a dictionary that represents individual nodes + """ + for node, value in _in.items(): + for state in self.state.__dict__.values(): + if state["grouping"] in value["upstreams"].keys(): + value["upstreams"][state["grouping"]][0]["fields"]["timestamp"]["content"] = state["timestamp"] + value["upstreams"][state["grouping"]][0]["fields"]["data"]["content"] = state["numpy"] + + return _in + + @lg.publisher(SERIALIZER_OUTPUT) async def source(self) -> lg.AsyncPublisher: - await asyncio.sleep(.01) + await asyncio.sleep(.1) while True: - msg = WSStreamMessage( - samples=self.config.data, + output_data = self.config.data + # check if the monitor is in real-time mode + if self.config.sub_pub_match: + # populate Serialized Graph with real-time data + output_data = { + key: self.output(value) for key, value in self.config.data.items() if key == "nodes" + } + # otherwise, only send the topology + yield self.SERIALIZER_OUTPUT, WSStreamMessage( + samples=output_data, stream_name=self.config.stream_name, stream_id=self.config.stream_id, ) - yield self.TOPIC, msg - await asyncio.sleep(1 / self.config.sample_rate) + await asyncio.sleep(1 / self.config.sample_rate), diff --git a/extensions/yaml_support/labgraph_monitor/tests/test_lg_monitor_api.py b/extensions/yaml_support/labgraph_monitor/tests/test_lg_monitor_api.py index 94870d6f4..304078009 100644 --- a/extensions/yaml_support/labgraph_monitor/tests/test_lg_monitor_api.py +++ b/extensions/yaml_support/labgraph_monitor/tests/test_lg_monitor_api.py @@ -21,7 +21,7 @@ def setUp(self) -> None: def test_identify_upstream_message(self) -> None: upstream_message = identify_upstream_message( - 'ROLLING_AVERAGER/INPUT', + 'ROLLING_AVERAGER/ROLLING_AVERAGER_INPUT', self.graph.__topics__ ) @@ -38,47 +38,47 @@ def test_out_edge_node_mapper(self) -> None: self.assertEqual(4, len(out_edge_node_map)) self.assertEqual( 'generate_noise', - out_edge_node_map['NOISE_GENERATOR/OUTPUT'].name + out_edge_node_map['NOISE_GENERATOR/NOISE_GENERATOR_OUTPUT'].name ) self.assertEqual( 'average', - out_edge_node_map['ROLLING_AVERAGER/OUTPUT'].name + out_edge_node_map['ROLLING_AVERAGER/ROLLING_AVERAGER_OUTPUT'].name ) self.assertEqual( 'amplify', - out_edge_node_map['AMPLIFIER/OUTPUT'].name + out_edge_node_map['AMPLIFIER/AMPLIFIER_OUTPUT'].name ) self.assertEqual( 'attenuate', - out_edge_node_map['ATTENUATOR/OUTPUT'].name + out_edge_node_map['ATTENUATOR/ATTENUATOR_OUTPUT'].name ) def test_in_out_edge_mapper(self) -> None: in_out_edge_map = in_out_edge_mapper(self.graph.__streams__.values()) self.assertEqual(6, len(in_out_edge_map)) self.assertEqual( - 'NOISE_GENERATOR/OUTPUT', - in_out_edge_map['ROLLING_AVERAGER/INPUT'] + 'NOISE_GENERATOR/NOISE_GENERATOR_OUTPUT', + in_out_edge_map['ROLLING_AVERAGER/ROLLING_AVERAGER_INPUT'] ) self.assertEqual( - 'NOISE_GENERATOR/OUTPUT', - in_out_edge_map['AMPLIFIER/INPUT'] + 'NOISE_GENERATOR/NOISE_GENERATOR_OUTPUT', + in_out_edge_map['AMPLIFIER/AMPLIFIER_INPUT'] ) self.assertEqual( - 'NOISE_GENERATOR/OUTPUT', - in_out_edge_map['ATTENUATOR/INPUT'] + 'NOISE_GENERATOR/NOISE_GENERATOR_OUTPUT', + in_out_edge_map['ATTENUATOR/ATTENUATOR_INPUT'] ) self.assertEqual( - 'ROLLING_AVERAGER/OUTPUT', - in_out_edge_map['SINK/INPUT_1'] + 'ROLLING_AVERAGER/ROLLING_AVERAGER_OUTPUT', + in_out_edge_map['SINK/SINK_INPUT_1'] ) self.assertEqual( - 'AMPLIFIER/OUTPUT', - in_out_edge_map['SINK/INPUT_2'] + 'AMPLIFIER/AMPLIFIER_OUTPUT', + in_out_edge_map['SINK/SINK_INPUT_2'] ) self.assertEqual( - 'ATTENUATOR/OUTPUT', - in_out_edge_map['SINK/INPUT_3'] + 'ATTENUATOR/ATTENUATOR_OUTPUT', + in_out_edge_map['SINK/SINK_INPUT_3'] ) def test_connect_to_upstream(self) -> None: @@ -89,9 +89,7 @@ def test_connect_to_upstream(self) -> None: self.assertEqual(expected_node_count, len(nodes)) def test_serialize_graph(self) -> None: - nodes = identify_graph_nodes(self.graph) - nodes = connect_to_upstream(nodes, self.graph.__streams__.values()) - serialized_graph = serialize_graph("Demo", nodes) + serialized_graph = serialize_graph(self.graph) self.assertEqual('Demo', serialized_graph["name"]) self.assertEqual(5, len(serialized_graph["nodes"])) diff --git a/setup_py36.py b/setup_py36.py index dea4091f1..a04188016 100644 --- a/setup_py36.py +++ b/setup_py36.py @@ -32,8 +32,10 @@ install_requires=[ "appdirs==1.4.3", "click==7.0", + "cppy==1.1.0", "dataclasses==0.6", "h5py==2.10.0", + "kiwisolver==1.3.2", "matplotlib==3.1.1", "mypy==0.782", "numpy==1.16.4", @@ -43,8 +45,8 @@ "pytest_mock==2.0.0", "pyzmq==18.1.0", "typeguard==2.5.1", - "typing_extensions>=3.7.4.3", + "typing_extensions==3.7.4.3", "websockets==8.1", "yappi==1.2.5", ], -) \ No newline at end of file +) diff --git a/signal_processing/README.md b/signal_processing/README.md new file mode 100644 index 000000000..ff8403e36 --- /dev/null +++ b/signal_processing/README.md @@ -0,0 +1,19 @@ +# Signal Processing + +## What this folder contains +Files in this directory contain signal processing examples used with LabGraph + +## Folder structure +- filter: Signal processing filters +- messages: LabGraph messages +- nodes: LabGraph nodes for signal processing +- synthetic: Synthetic signal generator and nodes +- synthetic_data: Synthetic data (currently only for continuous fNIRS signals) +- utils: utility files used in signal processing + +## Example use +- python3 -m pytest --pyargs signal_processing.filter +- python3 -m pytest --pyargs signal_processing.nodes + +## Main contributors +Main contributors for this folder are currently Sho Nakagome and Allen Yin. diff --git a/signal_processing/__init__.py b/signal_processing/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/filter/README.md b/signal_processing/filter/README.md new file mode 100644 index 000000000..4fd5c2255 --- /dev/null +++ b/signal_processing/filter/README.md @@ -0,0 +1,5 @@ +Files in this directory relate to filtering in the signal processing sense, +as processes that remove some unwanted components or features from a signal. + +Also include functions and transforms typically associated with signal processing, +e.g. STFT, FFT. diff --git a/signal_processing/filter/__init__.py b/signal_processing/filter/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/filter/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/filter/detrend.py b/signal_processing/filter/detrend.py new file mode 100644 index 000000000..f11a7d516 --- /dev/null +++ b/signal_processing/filter/detrend.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import logging + +import numpy as np +from scipy import signal + + +def _get_non_zero_index(data: np.array): + return data.nonzero()[0] + + +def interpolate_nan(y: np.ndarray): + nans = np.isnan(y) + y1 = y.copy() + y1[nans] = np.interp( + _get_non_zero_index(nans), _get_non_zero_index(~nans), y[~nans] + ) + return y1 + + +def detrend_and_offset(data: np.ndarray, fix_negative: bool = False) -> np.ndarray: + """ + Detrend time series by subtracting from it + a linear fit. Then offset it so the detrended + time series is centered on the temporal mean + of the original data. + + If `fix_negative` is True, then any data points that + become negative after the process will be interpolated. + Use this option when the input is DOT power measurements + that will be logmean'd afterward. + + Input: + - data: np.ndarray, data array of shape [n_time, n_channels] + - fix_negative: bool, if True then after detrend and offset, + any points that are negative will be interpolated to + be positive. This requires the mean of the original + data to be positive. + + Returns: + - output: np.ndarray, same shape as data, contains + the detrended and output signal + """ + detrended = signal.detrend(data, axis=0) + output = detrended + np.mean(data, axis=0, keepdims=True) + all_nan_channels = 0 + + if fix_negative: + if np.any(np.mean(data, axis=0) < 0): + raise ValueError("data mean is negative, cannot fix negative") + # check how many results <=0 + n_le0 = np.sum(output <= 0) + bad_ch = np.unique(np.argwhere(output <= 0)[:, 1]) + logging.info(f"bad_ch are {bad_ch}") + logging.info(f"Found {n_le0} points with values <=0, interpolating") + output[output <= 0] = np.nan + + for ch in bad_ch: + if np.all(np.isnan(output[:, ch])): + logging.warning("Channel all nans, skipping") + all_nan_channels += 1 + continue + output[:, ch] = interpolate_nan(output[:, ch]) + if all_nan_channels > 0: + logging.warning( + f"{all_nan_channels} channels all nans, check channel std and mean!" + ) + return output diff --git a/signal_processing/filter/fir_filters.py b/signal_processing/filter/fir_filters.py new file mode 100644 index 000000000..7bfa61f93 --- /dev/null +++ b/signal_processing/filter/fir_filters.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from typing import Tuple, Union + +import numpy as np +from scipy.signal import firwin, lfilter_zi, lfilter + + +def fir_coefficients_states( + cutoff: Union[float, list], fs: float, btype: bool, order: int +) -> Tuple[np.ndarray, np.ndarray]: + """Real-time equivalent FIR filter returning coefficients + + Args: + ---------- + cutoff: float or list + cutoff frequency to be specified + e.g. cutoff = 3 # Hz + when using bandpass or bandstop, pass list + e.g. [0.01, 3] = 0.01 Hz - 3 Hz + + fs: float + sampling frequency + + btype: bool + [default = True (lowpass). False = highpass] + In scipy 1.3.0 or above, the following can be used [TODO] + Options: ‘bandpass’, ‘lowpass’, ‘highpass’, ‘bandstop’ + Equivalent to btype in iir_filter + + order: int + filter order + + Returns: + ---------- + b: np.ndarray + coefficients of length num_taps FIR filter + + z: np.ndarray + the initial state for the filter + + Example of real-time emulation: + ---------- + # Get coefficients and initial state + b, z = fir_coefficients_states(cutoff=cutoff, + fs=fs, + btype='lowpass', + order=order) + # Initialize + filtered = np.zeros(data.size) + # Sample by sample + for i, x in enumerate(data): + filtered[i], z = lfilter(b, 1, [x], zi = z) + """ + + # Calculate the nyquist frequency + nyquist = fs / 2.0 + # Normalize the cutoff frequency using nyquist + normalized_cutoff = np.array(cutoff) / nyquist + # Design coefficients of length numtaps for FIR filter + b = firwin(numtaps=order, cutoff=normalized_cutoff, pass_zero=btype) + # Compute initial steady state of step response + z = lfilter_zi(b, 1) + + return b, z + + +def fir_filter( + data: np.ndarray, + cutoff: float, + fs: float, + btype: bool = True, + order: int = 50, + axis: int = -1, +) -> np.ndarray: + """FIR filtering returning filtered signal, + The filtered output will start at the same initial value as the input signal + by matching initial conditions. + + Note despite matching initial condition through the use of `lfilter_zi`, the + first few samples of the output signal will not be simply a delayed version. + The number of weird samples scale with the filter order. + + Args: + ---------- + data: np.ndarray + data to be filtered + + cutoff: float + cutoff frequency to be specified + e.g. cutoff = 3 # Hz + + fs: float + sampling frequency + + btype: bool + [default = True (lowpass). False = highpass] + In scipy 1.3.0 or above, the following can be used [TODO] + Options: ‘bandpass’, ‘lowpass’, ‘highpass’, ‘bandstop’ + Default: 'lowpass' + Equivalent to btype in iir + + order: int + filter order + + axis: int, dimension along which to apply the filter, same as that for `lfilter` + default is -1 + + Returns: + ---------- + filtered: np.ndarray + filtered data, same shape as data + """ + + # Get coefficients and initial state + b, z = fir_coefficients_states(cutoff=cutoff, fs=fs, btype=btype, order=order) + x_dims = data.shape + + # if multi-dimensional array, have to modify initial condition z + # to fit the dimension of the input data + if len(x_dims) > 1: + z_dims = np.ones(len(x_dims), dtype=int) + z_dims[axis] = -1 + z = z.reshape(z_dims) # convert to row vector + tile_dims = np.array(x_dims) + tile_dims[axis] = 1 + z = np.tile(z, tile_dims) + + filtered, _ = lfilter(b, 1, data, zi=z, axis=axis) + + return filtered + + +def check_symmetry(h: np.ndarray) -> bool: + """ + Given FIR filter coefficients `h`, check if it's symmetric + or antisymmetric + """ + N = len(h) + symmetry = all(np.isclose(h[k], h[N - k - 1]) for k in range(N)) + antisymmetry = all(np.isclose(h[k], -h[N - k - 1]) for k in range(N)) + if symmetry or antisymmetry: + return True + return False + + +def symmetric_fir_group_delay(h: np.ndarray) -> int: + """ + Group delay for symmetric or antisymmetric FIR filters is about + half of the filter length. + + If not symmetric, use `.signal_transforms.filter_group_delay` instead + """ + if not check_symmetry(h): + raise RuntimeError("FIR filter is not symmetric or antisymmetric!") + N = len(h) + if N % 2 == 1: + return (N - 1) // 2 + else: + return N // 2 diff --git a/signal_processing/filter/iir_filters.py b/signal_processing/filter/iir_filters.py new file mode 100644 index 000000000..d0504c364 --- /dev/null +++ b/signal_processing/filter/iir_filters.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from typing import Tuple, Union + +import numpy as np +from scipy.signal import ( + butter, + filtfilt, + lfilter, + sosfilt, + sosfiltfilt, + sosfilt_zi, +) + +from .signal_transforms import filter_group_delay + + +def butter_coefficients( + cutoff: Union[float, list], fs: float, btype: str, order=4 +) -> Tuple[np.ndarray, np.ndarray]: + """Butterworth filter coefficients + + Args: + ---------- + cutoff: float or list of floats + Cutoff frequency in Hz (e.g. 3 Hz) + fs: float + Sampling frequency in Hz + btype: str + Type of filter + Options: ['high', 'low', 'pass', 'stop'] + 'high': highpass + 'low': lowpass + 'pass': bandpass + 'stop': bandstop + order: int + Order of the filter + + Returns: + ---------- + b, a : ndarray, ndarray + Numerator and denominator polynomials + of the IIR filter + """ + + # calculate the nyquist frequency + nyquist = fs / 2.0 + # normalize + normalized_cutoff = np.asarray(cutoff) / nyquist + # calculate coefficients - analog=False to return a filter defined + # in z-domain, otherwise it's defined in s-domained and transformed + # through bi-linear transformation + b, a = butter(order, normalized_cutoff, btype=btype, analog=False) + return b, a + + +def butterworth_filter( + data: np.ndarray, + cutoff: Union[float, list], + fs: float, + btype: str, + axis: int = -1, + noDelay: bool = True, + order: int = 4, + **kwargs, +) -> np.ndarray: + """Butterworth filter using transfer function (tf) implementation. + For order higher than 4, it's recommended to use `butterworth_sos_filter` + for numerical stability. + + Please see butter_coefficients for details + in other attributes. + + Args: + ---------- + data: np.ndarray, array of data you want to filter. Last dimension + assumed to be time, following conventions of `lfilter` and `filtfilt` + Assumed to be of dimension [n_channels, n_time_samples] + + axis: dimension along which to apply the filter, same as the option + in `lfilter` and `filtfilt`. + If data is [n_time_samples, n_channels], then axis should be 0. + If data is [n_channels, n_time_samples], then axis is 1 or -1 (default). + + noDelay: flag if true, apply filter using `filtfilt`, which is zero-phase delay. + This means the filtered signal is not delayed w.r.t input. + Otherwise, filter is applied using `lfilter`. + + `lfilter` with butterworth should be linear phase in the passband. + `filtfilt` is guaranteed to be zero-phase, and by extension linear phase. + + By default with noDelay=False, `lfilter` output does not start at the + same initial value as the data input. Pass in values for 'zi' if this + is desired (values can be generated with `lfilter_zi`) + + cutoff, fs, btype, order: See `butter_coefficients`. + + **kwargs: Extra keyword args for `lfilter` (zi) or `filtfilt` (padtype, padlen) + + Returns: + -------- + out: np.ndarray, array of the filtered data. Same shape as data. + """ + + b, a = butter_coefficients(cutoff, fs, btype, order) + + if noDelay: + out = filtfilt(b, a, data, axis=axis, **kwargs) + else: + out = lfilter(b, a, data, axis=axis, **kwargs) + return out + + +def butter_sos( + cutoff: Union[float, list], fs: float, btype: str, order: int = 4 +) -> np.ndarray: + """Butterworth filter second-order section coefficients (sos) + + Args: + ---- + cutoff: float or list of floats + Cutoff frequency in Hz (e.g. 3 Hz) + fs: float + Sampling frequency in Hz + btype: str + Type of filter + Options: ['high', 'low', 'pass', 'stop'] + 'high': highpass + 'low': lowpass + 'pass': bandpass + 'stop': bandstop + order: int + Order of the filter + + Returns: + ---------- + sos : ndarray of second-order filter coefficients, must have shape (n_sections, 6). + Each row corresponds to a second-order section, with the first three columns + providing the numerator coefficients and the last three providing the + denominator coefficients. + """ + nyq = fs / 2.0 + normalized_cutoff = np.asarray(cutoff) / nyq + sos = butter(order, normalized_cutoff, btype, analog=False, output="sos") + return sos + + +def butterworth_sos_filter( + data: np.ndarray, + cutoff: Union[float, list], + fs: float, + btype: str, + axis: int = -1, + noDelay: bool = True, + order: int = 4, + **kwargs, +) -> np.ndarray: + """Butterworth filter using second-order sections (sos) implementation. + This is suitable for all frequency and filter order. However, for order + less than 4, use `butterworth_filter` for slightly better performance. + + Please see butter_sos for details in other attributes. + + Args: + ---------- + data: np.ndarray, array of data you want to filter. Last dimension + assumed to be time, following conventions of `lfilter` and `filtfilt` + Assumed to be of dimension [n_channels, n_time_samples] + + axis: dimension along which to apply the filter, same as the option + in `sosfilt` and `sosfiltfilt`. + If data is [n_time_samples, n_channels], then axis should be 0. + If data is [n_channels, n_time_samples], then axis is 1 or -1 (default). + + noDelay: flag if true, apply filter using `sosfiltfilt`, results + in zero-phase delay w.r.t input. Otherwise, filter is applied + using `sosfilt`. + + Using `sosfiltfilt` guarantees zero-phase and linear phase. + Using `sosfilt` with butterworth gives maximum linear-phase in the passband. + + By default with noDelay=False, `sos_filt` output does not start at the + same initial value as the data input. Pass in values for 'zi' if this + is desired (values can be generated with `sosfilt_zi`) + + cutoff, fs, btype, order: See `butter_sos`. + + **kwargs: Extra keyword args for `sosfilt` (zi), `sosfiltfilt` (padtype, padlen), + + Returns: + -------- + out: np.ndarray, array of the filtered data. Same shape as data. + """ + sos = butter_sos(cutoff=cutoff, fs=fs, btype=btype, order=order) + + if noDelay: + # sosfiltfilt + out = sosfiltfilt(sos, data, axis, **kwargs) + return out + else: + # sosfilt, with zi + out = sosfilt(sos, data, axis, **kwargs) + return out + + +def butter_group_delay( + cutoff: np.ndarray, fs: float, btype: str, order: int, N: int = 2048 +) -> Tuple[np.ndarray, np.ndarray]: + """ + Return frequency in Hz (ranging 0 to fs/2), + and group delay in samples + """ + sos_butter = butter_sos(cutoff, fs, btype, order) + freq, group_delay = filter_group_delay(sos_butter, N, fs, sos=True) + return freq, group_delay + + +def exponential_moving_average(x: np.ndarray, y_prev: np.ndarray, N: int) -> np.ndarray: + """EMA: Exponential Moving Average + + From the paper: + "Moving Average Convergence Divergence filter preprocessing for + real-time event-related peak activity onset detection: + application to fNIRS signals" + + Equations: + y_t = EMA(x) = \frac{2}{N+1} x_t + \frac{N-1}{N+1} y_{t-1} + + Arguments: + ---------- + x: raw signal input at time t + y_prev: filtered signal at time t-1 + N: parameter 1 = how many time samples to use for moving average + + Returns: + ---------- + y: filtered signal at time t + """ + + coeff_x = 2 / (N + 1) + coeff_y_prev = (N - 1) / (N + 1) + y = coeff_x * x + coeff_y_prev * y_prev + + return y + + +def moving_average_convergence_divergence( + x: np.ndarray, y_prev: np.ndarray, N_short: int, N_long: int +) -> np.ndarray: + """MACD: Moving Average Convergence/Divergence + + From the paper: + "Moving Average Convergence Divergence filter preprocessing for + real-time event-related peak activity onset detection: + application to fNIRS signals" + + Equations: + MACD(x_t) = EMA_short(x_t) - EMA_long(x_t) + + Arguments: + ---------- + x: raw signal input at time t + y_prev: filtered signal at time t-1 + N_short: parameter 1 = how many time samples to use for moving average + N_long: parameter 2 for long trend + + Returns: + ---------- + y: filtered signal at time t + """ + + ema_short = exponential_moving_average(x=x, y_prev=y_prev, N=N_short) + ema_long = exponential_moving_average(x=x, y_prev=y_prev, N=N_long) + + y = ema_short - ema_long + + return y + + +class CausalButterworth: + """Causal Butterworth Filter + + Can be used for both processing all the samples and + processing one sample (so that it can be combined) + """ + + # second-order sections + sos: np.ndarray # shape is [n_sections, 6], n_sections = ceil(order / 2) + # initial conditions + z: np.ndarray # shape [n_sections, 2, n_ch] + n_channels: int + + def __init__( + self, + first_time_sample: np.ndarray, + cutoff: Union[float, list], + sample_freq: float, + btype: str = "pass", + order: int = 4, + ) -> None: + """Initializing states for the filter + + first_time_sample: + The very first time sample at t = 0 + If one channel, data[0] + If multiple channels, data[0, :] + (given data = [time x channel]) + cutoff: + if 'high' or 'low' pass, float + if 'pass' or 'stop', List[float, float] + sample_freq: + sample frequency of the signal in Hz + btype: + type of filter. + Options: ['high', 'low', 'pass', 'stop'] + order: + filter order. + Note that this is IIR. + Use second-order sections and sosfilt for stability. + Note that the filter delay increases with filter order. + """ + + self.sos = butter_sos(cutoff, sample_freq, btype, order) + zi = sosfilt_zi(self.sos) # shape = [n_sections, 2] + if first_time_sample.ndim == 0: + self.n_channels = 1 + elif first_time_sample.ndim <= 2: + self.n_channels = first_time_sample.reshape((1, -1)).shape[1] + else: + raise RuntimeError("first_time_sample.ndim > 2") + self.z = np.tile(zi[:, :, None], (1, 1, self.n_channels)) + self.z = self.z * first_time_sample # shape = [n_sections, 2, n_ch] + + def process_sample(self, sample: np.ndarray) -> np.ndarray: + """Process sample causally + + This is so that you can combine with other process + per sample. (e.g. Log -> HbO/HbR -> this in one sample) + + sample: np.ndarray, assume to be a vector of shape [n_time, n_channel] + If n_dim is 1, assume to be [1, n_channel] sample + + ============ Ex. usage =========== + # Prepare data + fs = 100 # sampling frequency in Hz + duration_short = 1.0 # signal duration is 1 second + t_short = np.arange(0, duration_short, 1.0 / fs) + test_freq_short = [5, 25, 45] + data_short = np.hstack( + [ + np.cos(2 * np.pi * f * t_short).reshape([-1, 1]) + for f in test_freq_short + ] + ) # data_short.shape = (100, 3) + + # Filtering one channel only + cur_data = data_short[:, 0] + cb = CausalButterworth( + first_time_sample=cur_data[0], + cutoff=7, + sample_freq=fs, + btype='high' + ) + results = [] + # Process each sample causally + for i in range(len(data_short)): + results.append(cb.process_sample(sample=cur_data[i])) + results_np = np.vstack(results) + + ===== Multiple channels case ===== + cb = CausalButterworth( + first_time_sample=data_short[0, :], # all channels + cutoff=7, + sample_freq=fs, + btype='high' + ) + results = [] + # Process each sample causally + for i in range(data_short.shape[0]): + results.append(cb.process_sample(sample=data_short[i, :])) + results_np = np.vstack(results) + """ + sample = np.asarray(sample) + if sample.ndim == 0 and self.n_channels != 1: + raise RuntimeError( + f"Expected {self.n_channels} channels data, got 1 channel data" + ) + # reshape for ndim==0 and ndim==1 + sample = sample.reshape((1, -1)) + + if sample.shape[1] != self.n_channels: + raise RuntimeError( + f"Expected {self.n_channels} channels sample, received {sample.shape[1]} channel sample" + ) + output, self.z = sosfilt(self.sos, sample, axis=0, zi=self.z) + return output diff --git a/signal_processing/filter/signal_transforms.py b/signal_processing/filter/signal_transforms.py new file mode 100644 index 000000000..45e4ce3d4 --- /dev/null +++ b/signal_processing/filter/signal_transforms.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from typing import Tuple, Optional + +import numpy as np +from scipy.fftpack import fft, fftfreq, fftshift +from scipy.signal import sosfilt, unit_impulse, lfilter + + +def do_fft( + x: np.ndarray, + dt: float, + axis: int = -1, + dB: bool = False, +) -> Tuple[np.ndarray, np.ndarray]: + """Perform vanilla fft given then time-domain signal x and sampling time dt. + + Args: + ----------- + x: np.ndarray, data to perform fft on + + dt: float, sampling time of the signal in seconds + + axis: int, axis along wich to perform fft. Each sample along this axis is + considered to be sampled with dt + + dB: bool, flag to indicate whether the outputs will be returned in dB. + + Returns: + -------- + xf: np.ndarray, gives the two-sided frequency values for the fft. + + xFFT: np.ndarray, same shape as x, giving the magnitude of the fft + coefficients. If `dB`=True, then the result is `20*np.log10(xFFT)` + """ + + x_dims = x.shape + N = x_dims[axis] + xf = fftfreq(N) / dt + x_fft = fft(x, axis=axis) + xFFT = 1.0 / N * np.abs(x_fft) + + # move the values around so frequencies is from negative to positive + xf = fftshift(xf) + xFFT = fftshift(xFFT, axes=(axis,)) + + if dB: + xFFT = 20 * np.log10(xFFT) + + return xf, xFFT + + +def omega_to_f(omega: np.ndarray, fs: float) -> np.ndarray: + """ + Convert normalized frequency in radians/sample to frequency in Hz + """ + return fs * omega / (2 * np.pi) + + +def f_to_omega(f: np.ndarray, fs: float) -> np.ndarray: + """ + Convert frequency in Hz to normalized frequncy in radians/sample + """ + return f / fs * 2 * np.pi + + +def filter_impulse_response( + sos_or_fir_coef: np.ndarray, + N: int = 2048, + fs: float = None, + sos: bool = True, +) -> Tuple[np.ndarray, Optional[np.ndarray]]: + """ + Given a filter specification in second order sections (sos), + return the impulse response. + + Inputs: + - sos_or_fir_coef: np.ndarray, second order section of iir filter or fir_coef of a + FIR filter. + - N: int, number of samples to calculate for the impulse response + - fs: float, sampling rate in Hz. If not None, will return the time in seconds + corresponding to the samples. + - sos: bool. If true, assume `sos_or_fir_coef` is sos, otherwise as fir_coef + + Output: + - response: np.ndarray, impulse response values. By default, the time unit + is in samples. + - time: np.ndarray, if fs is not None, return the time in seconds. + """ + if sos: + response = sosfilt(sos_or_fir_coef, unit_impulse(N)) + else: + response = lfilter(b=sos_or_fir_coef, a=1, x=unit_impulse(N)) + if fs is not None: + time = np.arange(N) / fs + return response, time + else: + return response + + +def filter_freq_response( + sos: np.ndarray, N: int = 2048, fs: float = None +) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + Given filter specification in second order sections (sos), + return the frequency response by taking FFT of the impulse + response. + + Inputs: + - sos: np.ndarray, second order section of filter + - N: int, number of samples to calculate for the impulse and frequency response + - fs: float, sampling rate in Hz. If not None, will return the frequency in Hz, + otherwise normalized frequency will be returned. + + Output: + - frequency: np.ndarray, frequency of the frequency response. If fs is None, + unit will be in radians/sample (ranging from 0 to np.pi), + otherwise will be in Hz (ranging from 0 to fs / 2). + - magnitude: np.ndarray, magnitude of filter + - phase: np.ndarray, phase repsonse of filter, unit in radians. + """ + impulse_response = filter_impulse_response(sos, N) + + # Get frequency response + omega = (fftfreq(N) * 2 * np.pi)[0 : N // 2] + freq_response = fft(impulse_response)[0 : N // 2] + magnitude = np.abs(freq_response) + phase = np.angle(freq_response) + + if fs is not None: + freq = omega_to_f(omega, fs) + return freq, magnitude, phase + else: + return omega, magnitude, phase + + +def filter_phase_delay( + sos: np.ndarray, N: int = 2048, fs: float = None +) -> Tuple[np.ndarray, np.ndarray]: + """ + Given filter spec in second order sections of an IIR filter, return + phase delay in samples, extracted from the phase response + + Note for FIR filters, phase delay is constant and equal to either + (D/2) or (D-1)/2, where D is the number of coefficients in the filter. + + Inputs: + - sos: np.ndarray, second order section of filter + - N: int, number of samples to calculate for the impulse and frequency response + - fs: float, sampling rate in Hz. If not None, will return the frequency in Hz, + otherwise normalized frequency will be returned. + + Output: + - frequency: np.ndarray, frequency of the frequency response. If fs is None, + unit will be in radians/sample (ranging from 0 to np.pi), + otherwise will be in Hz (ranging from 0 to fs / 2). + - phase_delay: np.ndarray, phase delay of filter as function of frequency, unit + is in samples. + """ + omega, _, phase = filter_freq_response(sos, N) + phase_delay = -phase / omega + if fs is not None: + freq = omega_to_f(omega, fs) + return freq, phase_delay + else: + return omega, phase_delay + + +def filter_group_delay( + sos_or_fir_coef: np.ndarray, + N: int = 2048, + fs: float = None, + sos: bool = True, +) -> Tuple[np.ndarray, np.ndarray]: + """ + Given filter spec in second order sections or (num, den) form, return group delay. + Uses method in [1], which is cited by `scipy.signal.group_delay` but incorrectly implemented. + + Inputs: + - sos_or_fir_coef: np.ndarray, second order section of iir filter or fir_coef of a + FIR filter. + - N: int, number of samples to calculate for the impulse and frequency response + - fs: float, sampling rate in Hz. If not None, will return the frequency in Hz, + otherwise normalized frequency will be returned. + - sos: bool. If true, assume `sos_or_fir_coef` is sos, otherwise as fir_coef + + Output: + - frequency: np.ndarray, frequency of the frequency response. If fs is None, + unit will be in radians/sample (ranging from 0 to np.pi), + otherwise will be in Hz (ranging from 0 to fs / 2). + - group_delay: np.ndarray, group delay of filter as function of frequency, unit + is in samples. + + [1] Richard G. Lyons, "Understanding Digital Signal Processing, 3rd edition", p. 830. + """ + + impulse_response = filter_impulse_response(sos_or_fir_coef, N, sos=sos) + k = np.arange(N) + fft_gd = np.real(fft(k * impulse_response) / fft(impulse_response))[0 : N // 2] + omega = (fftfreq(N) * 2 * np.pi)[0 : N // 2] + if fs is not None: + freq = omega_to_f(omega, fs)[0 : N // 2] + return freq, fft_gd + else: + return omega, fft_gd diff --git a/signal_processing/filter/tests/__init__.py b/signal_processing/filter/tests/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/filter/tests/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/filter/tests/common.py b/signal_processing/filter/tests/common.py new file mode 100644 index 000000000..b904f09a1 --- /dev/null +++ b/signal_processing/filter/tests/common.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +# Defaulting to a gower phantom recording to use for tests +SAMPLE_PACKAGE_NAME = "sample" diff --git a/signal_processing/filter/tests/test_detrend.py b/signal_processing/filter/tests/test_detrend.py new file mode 100644 index 000000000..32742aa9a --- /dev/null +++ b/signal_processing/filter/tests/test_detrend.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import unittest + +import numpy as np + +from ..detrend import detrend_and_offset + + +class DetrendTest(unittest.TestCase): + def test_detrend_and_offset_positive_trend(self): + # signal is sine wave of different frequency + t = np.arange(0, 1, 0.01) + signal = np.vstack([np.cos(2 * np.pi * 5 * t), np.cos(2 * np.pi * 10 * t)]).T + + # Add DC offset + signal_with_offset = signal + 1 + + # Add linear trend, positive trend + linear_trend = t.reshape((-1, 1)) + + # composite signal + composite_signal = signal_with_offset + linear_trend + + # No need to fix negative because positive trend + signal_detrend_and_offset = detrend_and_offset( + composite_signal, fix_negative=False + ) + + # close to only 1 decimal place because + # detrend accumulates error due to least-square fit + np.testing.assert_array_almost_equal( + signal_detrend_and_offset, + signal + np.mean(composite_signal, axis=0), + decimal=1, + ) + + def test_detrend_and_offset_negative_trend(self): + # signal is sine wave of different frequency + t = np.arange(0, 1, 0.01) + signal = np.vstack([np.cos(2 * np.pi * 5 * t), np.cos(2 * np.pi * 10 * t)]).T + + # Add DC offset + signal_with_offset = signal + 1 + + # Add linear trend, positive trend + linear_trend = -t.reshape((-1, 1)) + + # composite signal + composite_signal = signal_with_offset + linear_trend + + # No need to fix negative because positive trend + signal_detrend_and_offset = detrend_and_offset( + composite_signal, fix_negative=False + ) + + np.testing.assert_array_almost_equal( + signal_detrend_and_offset, + signal + np.mean(composite_signal, axis=0), + decimal=1, + ) + + # now fix negative values and make sure that's correct + signal_detrend_and_offset_fix_negative = detrend_and_offset( + composite_signal, fix_negative=True + ) + self.assertTrue(np.all(signal_detrend_and_offset_fix_negative >= 0)) diff --git a/signal_processing/filter/tests/test_fir_filters.py b/signal_processing/filter/tests/test_fir_filters.py new file mode 100644 index 000000000..aa0e60278 --- /dev/null +++ b/signal_processing/filter/tests/test_fir_filters.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from __future__ import absolute_import, division, print_function, unicode_literals + +import unittest + +import mne +import numpy as np +from ..fir_filters import fir_filter + + +class FiniteImpulseResponseFilterTest(unittest.TestCase): + def setUp(self): + + import scipy + + print(scipy.__version__) + + # ===== 1) Preparing data (for lowpass) ===== + # time length + t_len = 50 + # sampling frequency + sample_freq = 10 + x = np.linspace(0, t_len, t_len * sample_freq, endpoint=False) + # making sure this has high power before filtering + signal_4Hz = 2 * np.sin(2 * np.pi * 4 * x) + signal_1Hz = np.sin(2 * np.pi * 1 * x) + combined = signal_4Hz + signal_1Hz + + # before filtering + power, freqs = mne.time_frequency.psd_array_multitaper(combined, sample_freq) + power_log10 = np.log10(power) + ind_of_peak = np.unravel_index( + np.argmax(power_log10, axis=None), power_log10.shape + )[0] + # make sure 4 Hz has higher power + self.assertEqual(freqs[ind_of_peak], 4.0) + + # Things needed for testing later + self.data = combined # data is [n_samples, ] + self.filt_order = 50 + self.sample_freq = sample_freq + + def check_peak_freq(self, data, cutoff, fs, btype, order, axis, peak_freq): + """Helper function to check that the filtered signal has the correct + peak-frequncies. + """ + filtered_data = fir_filter( + data=data, cutoff=cutoff, fs=fs, btype=btype, order=order, axis=axis + ) + + # psd_aray_multitaper requires the inputs to be of dimension: + # [..., n_times] + # axis is suppoed to be n_times + filtered_data_reshape = np.moveaxis(filtered_data, axis, -1) + power, freqs = mne.time_frequency.psd_array_multitaper( + filtered_data_reshape, fs + ) + power_log10 = np.log10(power) + ind_of_peak = np.argmax(power_log10, axis=-1) + self.assertEqual(np.all(freqs[ind_of_peak] == peak_freq), True) + + def test_fir_filter_1D(self): + """Test a simple FIR filter, input is one-dimensional + + 1) Create dummy sine waves with different frequencies + with one sine wave having higher amplitude = power + 2) Initial peak in PSD should show the frequency as dominant power + 3) Filter out using the filter + 4) This time the dominant power should be the other frequency + """ + + # ===== 2) Test lowpass ===== + # run filter lowpass + cutoff = 2 # Hz -- peak should be 1Hz + self.check_peak_freq( + data=self.data, + cutoff=cutoff, + fs=self.sample_freq, + btype=True, + order=self.filt_order, + axis=-1, + peak_freq=np.array([1.0]), + ) + + def test_fir_filter_nD(self): + """Test a simple lowpass FIR filter, input is multi-dimensional""" + cutoff = 2 # Hz + combined = np.vstack((self.data, self.data)) # dim is [2, n_samples] + self.check_peak_freq( + data=combined, + cutoff=cutoff, + fs=self.sample_freq, + btype=True, + order=self.filt_order, + axis=1, + peak_freq=np.array([1.0, 1.0]), + ) + + def test_fir_filter_axis(self): + """Test lowpass FIR filter works even on nx1 or 1xn data + + Creating the correct initial condition shape can be tricky + """ + cutoff = 2 + + # Check column vector works + col_data = self.data.reshape([-1, 1]) + print("test_fir_filter_axis: checking column vector input") + self.check_peak_freq( + data=col_data, + cutoff=cutoff, + fs=self.sample_freq, + btype=True, + order=self.filt_order, + axis=0, + peak_freq=np.array([1.0]), + ) + + # Check row vector works + row_data = self.data.reshape([1, -1]) + print("test_fir_filter_axis: checking row vector input") + self.check_peak_freq( + data=row_data, + cutoff=cutoff, + fs=self.sample_freq, + btype=True, + order=self.filt_order, + axis=1, + peak_freq=np.array([1.0]), + ) + + def test_fir_filter_linearPhase(self): + """Test lowpass FIR filter produces linear phase delay + + 1) Generate sample waveform + 2) Lowpass with different filter order + 3) Check that the different filtered signals are delayed by roughly + order/2 samples. This is done by looking through multiple periods + and find where the peaks are, and compare the sample number. + + Use different signals here to chose nice numbers to check + """ + + fs = 50 + duration = 1.0 + t = np.arange(0, duration, 1.0 / fs) + sin_10Hz = np.sin(2 * np.pi * 10 * t).reshape([-1, 1]) # col vector + cutoff = 20 # 20Hz lowpass, signal shouldn't be affected much + + # signal's peaks + nPeaks = 8 + signal_peaks = [] + for cur_order in [2, 4, 6, 8]: + y_LPF = fir_filter( + sin_10Hz, cutoff=cutoff, fs=fs, btype=True, order=cur_order, axis=0 + ) + y_LPF_peaks = [] + for i in range(nPeaks): + # find peaks -- period every 5 samples + idx_start = i * 5 + idx_end = (i + 1) * 5 + y_LPF_peaks.append(np.argmax(y_LPF[idx_start:idx_end])) + if len(signal_peaks) < nPeaks: + signal_peaks.append(np.argmax(sin_10Hz[idx_start:idx_end])) + + mean_delay = np.mean(np.array(y_LPF_peaks) - np.array(signal_peaks)) + # if mean_delay is negative, that means the first peak of the + # filtered signal is now closer to the second peak of the original + if mean_delay < 0: + print("mean_delay=" + str(mean_delay) + " adjust by period") + mean_delay = 5 + mean_delay + self.assertEqual(np.rint(mean_delay), cur_order / 2) diff --git a/signal_processing/filter/tests/test_iir_filters.py b/signal_processing/filter/tests/test_iir_filters.py new file mode 100644 index 000000000..7c86871b0 --- /dev/null +++ b/signal_processing/filter/tests/test_iir_filters.py @@ -0,0 +1,456 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import unittest +from functools import partial + +import numpy as np +from ..iir_filters import ( + butterworth_filter, + butterworth_sos_filter, + exponential_moving_average, + moving_average_convergence_divergence, + CausalButterworth, +) +from ..signal_transforms import do_fft +from ...synthetic_data.dummy_generator import dummy_physiological_noise +from scipy.signal import find_peaks + + +# Use FFT to check attenuation of bandpass +# Use peak finding to check for delay +# Use narrow passband to check difference between filtfilt and sosfiltfilt +class InfiniteImpulseResponseFilterTest(unittest.TestCase): + def setUp(self): + # Initializing sampling parameters and signals + + self.fs = 100 # sampling frequency in Hz + self.duration_short = 1.0 # signal duration is 1 second + self.t_short = np.arange(0, self.duration_short, 1.0 / self.fs) + + self.duration_long = 1000.0 + self.t_long = np.arange(0, self.duration_long, 1.0 / self.fs) + + # Nyquist is fs/2 = 50Hz, + # Create cosines of 5, 25, 45 Hz + # Data is of dimension [n_samples, n_channel] + # Nominal filtering data -- short duration + self.test_freq_short = [5, 25, 45] + self.data_short = np.hstack( + [ + np.cos(2 * np.pi * f * self.t_short).reshape([-1, 1]) + for f in self.test_freq_short + ] + ) + + # Use FFT for checking frequency attenuation + xf, xFFT = do_fft(self.data_short, dt=1.0 / self.fs, axis=0, dB=False) + self.freq_idx_short = np.hstack( + [np.argmin(np.abs(xf - i)) for i in self.test_freq_short] + ) + self.mag_short = np.diag(xFFT[self.freq_idx_short, :]) + print(f"magnitude for {self.test_freq_short} is {self.mag_short}") + # Use peak finding to check zero-phase delay + self.peaks_idx_short = [ + find_peaks(self.data_short[:, i], height=0.5)[0] + for i in range(len(self.test_freq_short)) + ] + + # Create cosines of [0.01, 0.05, 0.1] Hz for testing filter stability + # Low frequency, long duration + self.test_freq_long = [0.01, 0.05, 0.1] + self.data_long = np.hstack( + [ + np.cos(2 * np.pi * f * self.t_long).reshape([-1, 1]) + for f in self.test_freq_long + ] + ) + + # Use FFT for checking frequency attenuation + xf, xFFT = do_fft(self.data_long, dt=1.0 / self.fs, axis=0, dB=False) + self.freq_idx_long = np.hstack( + [np.argmin(np.abs(xf - i)) for i in self.test_freq_long] + ) + self.mag_long = np.diag(xFFT[self.freq_idx_long, :]) + + # Use peak finding to check zero-phase delay + self.peaks_idx_long = [ + find_peaks(self.data_long[:, i], height=0.5)[0] + for i in range(len(self.test_freq_long)) + ] + + # Alias for the functions to test + self.do_lfilter = partial(butterworth_filter, noDelay=False) + self.do_filtfilt = partial(butterworth_filter, noDelay=True) + self.do_sosfilt = partial(butterworth_sos_filter, noDelay=False) + self.do_sosfiltfilt = partial(butterworth_sos_filter, noDelay=True) + + self.filter_funcs = [ + (self.do_lfilter, "lfilter"), + (self.do_filtfilt, "filtfilt"), + (self.do_sosfilt, "sosfilt"), + (self.do_sosfiltfilt, "sosfiltfilt"), + ] + + def check_attenuation( + self, filtered_data, ref_val, truth, doPrint=False, checkFor=True + ): + """Helper function to check if the frequencies are attenuated. + + Args: + --- + filterd_data: 2D ndarray of filtered data. Assumed to be + [n_samples, n_channel] + ref_val: str, 'short' or 'long', to indicate which data set to + check against + truth: length n_freq list of booleans indicating whether + each frequency should be attenuated. + doPrint: boolean -- true to print out the Vout/Vin ratio. + checkFor: boolean -- check that the given truth values match or not + """ + + # Use FFT to check frequency of the filtered data + xf, xFFT = do_fft(filtered_data, dt=1.0 / self.fs, axis=0, dB=False) + if ref_val == "short": + mag_new = np.diag(xFFT[self.freq_idx_short, :]) + ratio = mag_new / self.mag_short + elif ref_val == "long": + mag_new = np.diag(xFFT[self.freq_idx_long, :]) + ratio = mag_new / self.mag_long + if doPrint: + print("Vout/Vin: ratio") + + answer = ratio <= np.sqrt(2) / 2 # -3dB attenuation as threshold + self.assertEqual(np.all(answer == truth), checkFor) + + def test_butterworth_filter_shape_and_attenuation(self): + """Test butter worth filter's shape and attenuation + + 1) Filter the signals using different filters: + lowpass, highpass, and bandpass + 2) Check output shape is correct + 3) Check the correct frequencies are attenuated + + To check frequency attenuation, check that the filtered signals' + max value is at least 10% reduced compared from before. This attenuation + is dependent on the cutoff frequency, and the test-frequencies are + spaced far apart to detect this difference. + + This method only works when `linearPhase=False` because `filtfilt` will + try to match the boundary conditions which we don't want. + """ + + print(f"Data shape = {self.data_short.shape}") + # Now applying filter + # 1) highpass (cutoff 7Hz, 4th order, lfilter) + y_HPF = self.do_lfilter( + self.data_short, cutoff=7, fs=self.fs, btype="high", order=4, axis=0 + ) + print(f"tf_filter: Data shape after highpass filter = {y_HPF.shape}") + self.assertEqual(y_HPF.shape, self.data_short.shape) + + # high pass filtering at 7Hz, that means 5Hz are attenuated + # are unaffected + truth = [True, False, False] + self.check_attenuation(y_HPF, "short", truth) + + y_HPF = self.do_sosfilt( + self.data_short, cutoff=7, fs=self.fs, btype="high", order=4, axis=0 + ) + print(f"sos_filter: Data shape after highpass filter = {y_HPF.shape}") + self.assertEqual(y_HPF.shape, self.data_short.shape) + self.check_attenuation(y_HPF, "short", truth) + + # 2) bandpass (cutoff 7 - 40 Hz) + y_BPF = self.do_lfilter( + self.data_short, + cutoff=[7, 40], + fs=self.fs, + btype="pass", + order=4, + axis=0, + noDelay=False, + ) + print(f"tf_filter: Data shape after bandpass filter = {y_BPF.shape}") + self.assertEqual(y_BPF.shape, self.data_short.shape) + + # band pass filtering at 7-40 Hz, that means 5Hz and 45Hz are attenuated + truth = [True, False, True] + self.check_attenuation(y_BPF, "short", truth) + + y_BPF = self.do_sosfilt( + self.data_short, cutoff=[7, 40], fs=self.fs, btype="pass", order=4, axis=0 + ) + print(f"sos_filter: Data shape after bandpass filter = {y_BPF.shape}") + self.assertEqual(y_BPF.shape, self.data_short.shape) + self.check_attenuation(y_BPF, "short", truth) + + # 3) LPF (cutoff 40Hz) + y_LPF = self.do_lfilter( + self.data_short, cutoff=40, fs=self.fs, btype="low", order=4, axis=0 + ) + print(f"tf_filter: Data shape after lowpass filter = {y_LPF.shape}") + self.assertEqual(y_LPF.shape, self.data_short.shape) + + # low pass filtering at 40 Hz, that means 45Hz is attenuated + truth = [False, False, True] + self.check_attenuation(y_LPF, "short", truth) + + y_LPF = self.do_sosfilt( + self.data_short, cutoff=40, fs=self.fs, btype="low", order=4, axis=0 + ) + print(f"sos_filter: Data shape after lowpass filter = {y_LPF.shape}") + self.assertEqual(y_LPF.shape, self.data_short.shape) + self.check_attenuation(y_LPF, "short", truth) + + def test_causal_butterworth(self): + """Causal butterworth filter test""" + # ===== 1) high pass ===== + cb = CausalButterworth( + first_time_sample=self.data_long[0, :], + cutoff=0.03, + sample_freq=self.fs, + btype="high", + ) + results = [] + for i in range(self.data_long.shape[0]): + results.append(cb.process_sample(sample=self.data_long[i, :])) + y_HPF = np.vstack(results) + + # Check dimension + self.assertEqual(np.all(self.data_long), np.all(y_HPF)) + + # Check stability + self.assertTrue(np.sum(np.isnan(y_HPF)) == 0) + + # high pass filtering at 0.03Hz, that means 0.01Hz are attenuated + # are unaffected + truth = [True, False, False] + self.check_attenuation(y_HPF, "long", truth) + + # ===== 2) band pass ===== + cb = CausalButterworth( + first_time_sample=self.data_long[0, :], + cutoff=[0.02, 0.08], + sample_freq=self.fs, + btype="pass", + ) + results = [] + for i in range(self.data_long.shape[0]): + results.append(cb.process_sample(sample=self.data_long[i, :])) + y_BPF = np.vstack(results) + + self.assertEqual(y_BPF.shape, self.data_long.shape) + + # Check stability + self.assertTrue(np.sum(np.isnan(y_BPF)) == 0) + + # band pass filtering at 0.02-0.08 Hz, that means 0.01Hz and 0.1Hz are attenuated + truth = [True, False, True] + self.check_attenuation(y_BPF, "long", truth) + + # ===== 3) low pass ===== + cb = CausalButterworth( + first_time_sample=self.data_long[0, :], + cutoff=0.08, + sample_freq=self.fs, + btype="low", + ) + results = [] + for i in range(self.data_long.shape[0]): + results.append(cb.process_sample(sample=self.data_long[i, :])) + y_LPF = np.vstack(results) + + self.assertEqual(y_LPF.shape, self.data_long.shape) + + # Check stability + self.assertTrue(np.sum(np.isnan(y_LPF)) == 0) + + # low pass filtering at 0.08 Hz, that means 0.1Hz is attenuated + truth = [False, False, True] + self.check_attenuation(y_LPF, "long", truth) + + def test_butterworth_filter_axis(self): + """Make sure when changing axis option, the results are different, + if using the wrong option the results should not be right. + """ + + # Use the bandpass example, but filter channel-wise + y_BPF = self.do_lfilter( + self.data_short, cutoff=[7, 40], fs=self.fs, btype="pass", order=4, axis=1 + ) + print(f"Data shape after bandpass filter(axis=1) = {y_BPF.shape}") + self.assertEqual(y_BPF.shape, self.data_short.shape) + + # band pass filtering at 7-40 Hz, that means 5Hz and 45Hz are attenuated + truth = [True, False, True] + # Make sure the result is not correct + self.check_attenuation(y_BPF, "short", truth, checkFor=False) + + # Do it correctly! + y_BPF = self.do_lfilter( + self.data_short, cutoff=[7, 40], fs=self.fs, btype="pass", order=4, axis=0 + ) + print(f"Data shape after bandpass filter(axis=0) = {y_BPF.shape}") + self.assertEqual(y_BPF.shape, self.data_short.shape) + self.check_attenuation(y_BPF, "short", truth, checkFor=True) + + # Check stability + def test_butterworth_filter_stability(self): + """Test to make sure that sos filters are stable even for high + filter order and narrow passband, while tf-filters are not. + (If the second part of the test break, then that means scipy + has changed..?) + """ + + # Test 1, data_short for testing + # BPF[10, 30], 4th order + # All filters should pass + cutoff = np.array([10, 30]) + filter_order = 4 + for (func, func_name) in self.filter_funcs: + print(f"Order {filter_order}, BPF({cutoff}), {func_name}") + out = func( + data=self.data_short, + cutoff=cutoff, + fs=self.fs, + btype="pass", + axis=0, + order=filter_order, + ) + self.check_attenuation(out, "short", [True, False, True]) + + # Test 2, data_long for testing + # BPF[0.02, 0.08], 4th order + # Only the sos filters should pass + filter_order = 4 + cutoff = np.array([0.02, 0.08]) + for (func, func_name) in self.filter_funcs[0:2]: + print(f"Order {filter_order}, BPF({cutoff}), {func_name}") + out = func( + data=self.data_long, + cutoff=cutoff, + fs=self.fs, + btype="pass", + axis=0, + order=filter_order, + ) + self.check_attenuation(out, "long", [True, False, True], checkFor=False) + + for (func, func_name) in self.filter_funcs[2:]: + print(f"Order {filter_order}, BPF({cutoff}), {func_name}") + out = func( + data=self.data_long, + cutoff=cutoff, + fs=self.fs, + btype="pass", + axis=0, + order=filter_order, + ) + self.check_attenuation(out, "long", [True, False, True], checkFor=True) + + # Check noDelay + def test_butterworth_filter_noDelay(self): + """Test to make sure that the noDelay option is working, + really just means that we are using either `filtfilt` vs. `lfilter`, + or `sosfilt` vs. `sosfiltfilt` here. + + Since the testing signals are cosine, y(0)=1 + + Best way to check this is to see if the filtered signals' peaks + line up in time with that of the original signal's. + """ + + filter_order = 4 + cutoff = np.array([10, 30]) + + # Bandpass on data_short , check the peaks for the 25Hz signal + # For lfilter and sos_filt + for (func, func_name) in [self.filter_funcs[0], self.filter_funcs[2]]: + print(f"Order {filter_order}, BPF({cutoff}), {func_name} peak-check") + out = func( + data=self.data_short, + cutoff=cutoff, + fs=self.fs, + btype="pass", + axis=0, + order=filter_order, + ) + peaks_new, _ = find_peaks(out[:, 1], height=0.5) + # Should have the same number of peaks + self.assertEqual(len(peaks_new), len(self.peaks_idx_short[1])) + # The peaks should be spaced more than at least 1 samples apart + self.assertTrue(np.all(np.abs(peaks_new - self.peaks_idx_short[1]) >= 1)) + + # For filtfilt and sos_filtfilt + for (func, func_name) in [self.filter_funcs[1], self.filter_funcs[3]]: + print(f"Order {filter_order}, BPF({cutoff}), {func_name} peak-check") + out = func( + data=self.data_short, + cutoff=cutoff, + fs=self.fs, + btype="pass", + axis=0, + order=filter_order, + ) + peaks_new, _ = find_peaks(out[:, 1], height=0.5) + # Should have the same number of peaks + self.assertEqual(len(peaks_new), len(self.peaks_idx_short[1])) + # The peaks should not be spaced more than at least 1 samples apart + self.assertTrue(np.all(np.abs(peaks_new - self.peaks_idx_short[1]) < 1)) + + def test_macd_filters(self): + # Defining parameters to generate synthetic data + duration = 200 # seconds + sample_freq = 5 # Hz + + # ===== Create simulated data ===== + cardiac = dummy_physiological_noise( + amplitude=0.3, + sample_rate=sample_freq, + interest_freq=1, + phase=np.pi / 4, + duration=duration, + ) + slow_drift = dummy_physiological_noise( + amplitude=0.1, + sample_rate=sample_freq, + interest_freq=0.01, + phase=0, + duration=duration, + ) + interest_freq = 0.1 + important = dummy_physiological_noise( + amplitude=0.3, + sample_rate=sample_freq, + interest_freq=interest_freq, + phase=0, + duration=duration, + ) + signal = cardiac + slow_drift + important + + # ===== Do filtering (Causal) ===== + macd_results = [0] + ema_on_macd = [0] + for i in range(1, len(signal)): + temp1 = moving_average_convergence_divergence( + x=signal[i], + y_prev=macd_results[i - 1], + N_short=sample_freq * 5, + N_long=sample_freq * 15, + ) + macd_results.append(temp1) + temp2 = exponential_moving_average( + x=macd_results[i], y_prev=ema_on_macd[i - 1], N=10 + ) + ema_on_macd.append(temp2) + ema_on_macd_np = np.array(ema_on_macd) + + # ===== Apply FFT ===== + xf_ema_macd, xFFT_ema_macd = do_fft( + ema_on_macd_np, dt=1.0 / sample_freq, axis=0, dB=False + ) + + # ===== Get the peak, make sure it's the same frequency of interest ===== + peaks_ema_macd = find_peaks(xFFT_ema_macd, distance=duration * sample_freq / 2) + self.assertAlmostEqual(*xf_ema_macd[peaks_ema_macd[0]], interest_freq) diff --git a/signal_processing/filter/tests/test_signal_transforms.py b/signal_processing/filter/tests/test_signal_transforms.py new file mode 100644 index 000000000..2db9734b6 --- /dev/null +++ b/signal_processing/filter/tests/test_signal_transforms.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from __future__ import absolute_import, division, print_function, unicode_literals + +import unittest + +import numpy as np +import scipy +from ..fir_filters import symmetric_fir_group_delay +from ..iir_filters import butter_group_delay, butter_sos, CausalButterworth +from ..signal_transforms import ( + do_fft, + filter_freq_response, + filter_group_delay, + filter_impulse_response, + filter_phase_delay, +) + + +class SignalTransformsTest(unittest.TestCase): + def setUp(self): + self.dt = 1 / 100 + self.t = np.arange(0, 10, self.dt) # 100Hz + + # FFT magnitude for the different freq + self.mag_10Hz = 5 + self.mag_15Hz = 2.5 + self.mag_DC = 1 + # [n_samples, ] + self.x_1D = ( + 2 * self.mag_10Hz * np.cos(2 * np.pi * 10 * self.t) + + 2 * self.mag_15Hz * np.cos(2 * np.pi * 15 * self.t) + + self.mag_DC + ) + + # [n_samples, 2] + self.x_2D = np.hstack((self.x_1D.reshape([-1, 1]), self.x_1D.reshape([-1, 1]))) + + # [2, n_samples, 2] + self.x_3D = np.concatenate( + (np.expand_dims(self.x_2D, axis=0), np.expand_dims(self.x_2D, axis=0)), + axis=0, + ) + # second 'page' has no DC + self.x_3D[1, :, :] = self.x_3D[1, :, :] - 1 + + # Basic 1D fft + def test_FFT_1D(self): + xf, xFFT = do_fft(self.x_1D, self.dt) + + # Check 10Hz freq component + self.assertAlmostEqual(xFFT[np.argwhere(xf == 10)][0][0], self.mag_10Hz, 3) + self.assertAlmostEqual(xFFT[np.argwhere(xf == -10)][0][0], self.mag_10Hz, 3) + + # Check 15Hz freq component + self.assertAlmostEqual(xFFT[np.argwhere(xf == 15)][0][0], self.mag_15Hz, 3) + self.assertAlmostEqual(xFFT[np.argwhere(xf == -15)][0][0], self.mag_15Hz, 3) + + # Check DC freq component + self.assertAlmostEqual(xFFT[np.argwhere(xf == 0)][0][0], self.mag_DC, 3) + + # Use dB values + xf, xFFT_dB = do_fft(self.x_1D, self.dt, dB=True) + # Check 10Hz freq component + val = 20 * np.log10(self.mag_10Hz) + self.assertAlmostEqual(xFFT_dB[np.argwhere(xf == 10)][0][0], val, 3) + self.assertAlmostEqual(xFFT_dB[np.argwhere(xf == -10)][0][0], val, 3) + + # Check 15Hz freq component + val = 20 * np.log10(self.mag_15Hz) + self.assertAlmostEqual(xFFT_dB[np.argwhere(xf == 15)][0][0], val, 3) + self.assertAlmostEqual(xFFT_dB[np.argwhere(xf == -15)][0][0], val, 3) + + # Check DC freq component + val = 20 * np.log10(self.mag_DC) + self.assertAlmostEqual(xFFT_dB[np.argwhere(xf == 0)][0][0], val, 3) + + # 2D FFT + def test_FFT_2D(self): + xf, xFFT = do_fft(self.x_2D, self.dt, axis=0) + + # Check 10Hz freq component + self.assertEqual( + np.all(xFFT[np.argwhere(xf == 10)[0][0], :] - self.mag_10Hz < 1e-3), True + ) + self.assertEqual( + np.all(xFFT[np.argwhere(xf == -10)[0][0], :] - self.mag_10Hz < 1e-3), True + ) + + # Check 15Hz freq component + self.assertEqual( + np.all(xFFT[np.argwhere(xf == 15)[0][0], :] - self.mag_15Hz < 1e-3), True + ) + self.assertEqual( + np.all(xFFT[np.argwhere(xf == -15)[0][0], :] - self.mag_15Hz < 1e-3), True + ) + + # Check DC freq component + self.assertEqual( + np.all(xFFT[np.argwhere(xf == 0)[0][0], :] - self.mag_DC < 1e-3), True + ) + + # 3D FFT + def test_FFT_3D(self): + xf, xFFT = do_fft(self.x_3D, self.dt, axis=1) + + # Check 10Hz freq component + for page in range(1): + for f in [10, -10]: + idx = np.argwhere(xf == f)[0][0] + self.assertEqual( + np.all(xFFT[page, idx, :] - self.mag_10Hz < 1e-3), True + ) + + # Check 15Hz freq component + for page in range(1): + for f in [15, -15]: + idx = np.argwhere(xf == f)[0][0] + self.assertEqual( + np.all(xFFT[page, idx, :] - self.mag_15Hz < 1e-3), True + ) + + # Check DC freq component + self.assertEqual( + np.all(xFFT[0, np.argwhere(xf == 0)[0][0], :] - self.mag_DC < 1e-3), True + ) + # Second page has no DC component + self.assertEqual( + np.all(xFFT[1, np.argwhere(xf == 0)[0][0], :] - 0 < 1e-3), True + ) + + def test_filter_impulse_response(self): + """ + Get impulse response of 2048 points, for 5th-order IIR filter + with bandwidth [0.01, 0.1]Hz, sampling frequency of 10Hz + """ + N = 2048 + fs = 10.0 + bandwidth = (0.01, 0.1) + + order = 5 + sos_iir = butter_sos(bandwidth, fs=fs, btype="bandpass", order=order) + iir_impulse_response, time = filter_impulse_response(sos_iir, N, fs, sos=True) + + self.assertTrue( + np.abs(iir_impulse_response[0]) < 1e-6 + ) # impulse response starts at 0 + np.testing.assert_array_almost_equal( + iir_impulse_response, + scipy.signal.sosfilt(sos_iir, scipy.signal.unit_impulse(N)), + ) + np.testing.assert_array_almost_equal(time, np.arange(0, 2048 / fs, 1 / fs)) + + """ + Get impulse response of 2048 points, for a 25-tap FIR filter with + bandwidth [0.01, 0.1]Hz, sampling frequency of 10Hz + """ + n_coefs = 25 + fir_coefs = scipy.signal.firwin(n_coefs, bandwidth, pass_zero=False, fs=fs) + fir_impulse_response, time = filter_impulse_response( + fir_coefs, N, fs, sos=False + ) + self.assertTrue( + fir_impulse_response[-1] == 0 + ) # FIR impulse response is finite! + np.testing.assert_array_almost_equal( + fir_impulse_response, + scipy.signal.lfilter(b=fir_coefs, a=1, x=scipy.signal.unit_impulse(N)), + ) + + def test_filter_freq_response(self): + """ + Get frequency response of a 5th-order IIR filter + with bandwidth [0.01, 0.1]Hz, sampling frequency of 10Hz + """ + N = 2048 + fs = 10.0 + bandwidth = (0.01, 0.1) + order = 5 + sos_iir = butter_sos(bandwidth, fs=fs, btype="bandpass", order=order) + + # check freq response is roughly flat between 0.01 and 0.06Hz + freq, magnitude, phase = filter_freq_response(sos_iir, N, fs) + np.testing.assert_array_almost_equal( + magnitude[(freq >= 0.01) & (freq <= 0.06)], 1, decimal=2 + ) + # check phase response at 0 starts at -pi + # sosfreqz has phase response starting at 0 + self.assertTrue(np.abs(phase[0] - -np.pi) < 1e-3) + + def test_filter_phase_delay_iir(self): + """ + Check phase delay at near center band for 5th order + butterworth bandpass filter, with bandwidth (0.01, 0.1)Hz, + sampling frequency of 10Hz. + + The phase delay at 0.05Hz should be about 33 samples. + """ + N = 2048 + fs = 10.0 + bandwidth = (0.01, 0.1) + test_freq = 0.05 + order = 5 + sos_iir = butter_sos(bandwidth, fs=fs, btype="bandpass", order=order) + freq, phase_delay = filter_phase_delay(sos_iir, N, fs) + self.assertTrue( + np.abs(phase_delay[np.argmin(np.abs(freq - test_freq))] - 33) < 1 + ) + + def test_filter_group_delay_fir(self): + """ + Group delay calculated via impulse response and + symmetric_fir_group_delay should match for + over 98% of the frequencies (mismatch due to numerical + errors) + """ + n_coefs = 25 + bandwidth = (0.01, 0.1) + fs = 10 + fir_coefs = scipy.signal.firwin(n_coefs, bandwidth, pass_zero=False, fs=fs) + symmetric_gd = symmetric_fir_group_delay(fir_coefs) + _, impulse_gd = filter_group_delay(fir_coefs, fs=fs, sos=False) + self.assertTrue( + np.sum(np.abs(impulse_gd - symmetric_gd) < 1) / len(impulse_gd) >= 0.98 + ) diff --git a/signal_processing/messages/generic_signal_sample.py b/signal_processing/messages/generic_signal_sample.py new file mode 100644 index 000000000..9c9cd1f60 --- /dev/null +++ b/signal_processing/messages/generic_signal_sample.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from typing import Optional + +import labgraph as lg +import numpy as np + + +class SignalSampleMessage(lg.TimestampedMessage): + # The actual shape of this depends on the configuration + sample: np.ndarray + """ + This field is useful in filtering and signal processing nodes. + - `timestamp` (inherited from lg.TimestampedMessage) represents + time when the message is created, this is useful probably + for labgraph message syncing purposes. + - `sample_timestamp` here represents the absolute timestamp for which + the current message corresponds to (for causal filtering, this is + in the past compared to `__super__.timestamp. + The default value cannot be nan because writing None-type does not + have dtype and cannot be written to hdf5. + """ + sample_timestamp: float = np.nan + + +class BooleanSampleMessage(lg.TimestampedMessage): + sample: np.ndarray + sample_timestamp: float = np.nan diff --git a/signal_processing/nodes/__init__.py b/signal_processing/nodes/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/nodes/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/nodes/faucet/__init__.py b/signal_processing/nodes/faucet/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/nodes/faucet/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/nodes/faucet/iir_filter_node.py b/signal_processing/nodes/faucet/iir_filter_node.py new file mode 100644 index 000000000..6c0dccb11 --- /dev/null +++ b/signal_processing/nodes/faucet/iir_filter_node.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +""" Node for a IIR filter for causal Faucet """ + +import asyncio +import logging +import time +from dataclasses import field +from typing import List, Optional, Union + +import labgraph as lg +import numpy as np +from ...messages.generic_signal_sample import SignalSampleMessage +from ...filter.iir_filters import CausalButterworth + + +# Define states and configs +class CausalButterFilterNodeConfig(lg.Config): + """Sampling frequency in Hz + + sample_rate: sampling frequency in Hz + highpass_cutoff: highpass cutoff in Hz + highpass_order: order of highpass filter + lowpass_cutoffs: List of lowpass filter cutoff frequencies. + length of this list is the total order of lowpass + + Online estimate of sampling frequency: + + If sample_rate was not given when node is initialized, + the filter node will fill a buffer of length `initial_buffer_len` + messages, and derive the sample_rate from their timestamps. This + assumes uniform sampling. Default is 10 samples, in which case the + output will be non-null at the second sample. + """ + + sample_rate: float = -1.0 + initial_buffer_len: int = 10 + + highpass_cutoff: Optional[float] = 0.01 + highpass_order: Optional[int] = 5 + lowpass_cutoffs: List = field(default_factory=list) + + """ + Option to invert the input sample. + Faucet expects the input to bandpass is -log(power_measurement). + Node outputs log(power_measurements). If bandpass is the first + filtering stage, the default `invert_input=True` would take care + of the inversion. + + If used for something else (e.g. IMU, etc), set this appropriately! + """ + invert_input: bool = True + pass_through: bool = False + + def __post_init__(self): + super().__post_init__() + if self.sample_rate < 0 and self.initial_buffer_len <= 0: + raise ValueError( + "initial_buffer_len needs to be greater than 0 when sample rate unspecified" + ) + + +class DefaultBandpassConfigGenerator: + def getConfig(): + return CausalButterFilterNodeConfig( + sample_rate=-1.0, # estimate sample rate + initial_buffer_len=10, + highpass_cutoff=0.01, + highpass_order=5, + lowpass_cutoffs=[0.1, 0.1, 0.1, 0.1, 0.1], + invert_input=True, + pass_through=False, + ) + + +class CausalButterFilterState(lg.State): + initial_buffer: List[SignalSampleMessage] = field(default_factory=list) + filters: List[CausalButterworth] = field(default_factory=list) + sample_rate: float = -1.0 + + +class CausalButterFilterNode(lg.Node): + """ + If the input SignalSampleMessage has an empty + `sample_timestamp`, then it's assumed this is the + first casual filter of the signal processing chain + and follows directly after a data source. + + Otherwise, the sample rate estimation will be calculated + from the `sample_timestamp` field of the SignalSampleMessage. + + This is because in the second case, the sample rate is best + estimated from the timestamps of the raw messages directly + from the data source. + """ + + # Subscribed topics + INPUT = lg.Topic(SignalSampleMessage) + TERMINATION_TOPIC = lg.Topic(lg.TerminationMessage) + + # Published topics + OUTPUT = lg.Topic(SignalSampleMessage) + + state: CausalButterFilterState + config: CausalButterFilterNodeConfig + + def setup(self) -> None: + self._shutdown = asyncio.Event() + # Have to be assigned here because config cannot be modified in runtime + self.state.sample_rate = self.config.sample_rate + + @lg.subscriber(TERMINATION_TOPIC) + def terminate(self, message: lg.TerminationMessage) -> None: + self._shutdown.set() + raise lg.NormalTermination() + + def _init_filter_class(self, message: SignalSampleMessage) -> bool: + # Returns True if filter class is initialized + + if len(self.state.filters) > 0: + return True + + self.state.initial_buffer.append(message) + if self.state.sample_rate < 0: + if len(self.state.initial_buffer) <= self.config.initial_buffer_len: + return False + if not np.isnan(self.state.initial_buffer[0].sample_timestamp): + timestamps = np.array( + [message.sample_timestamp for message in self.state.initial_buffer] + ) + else: + timestamps = np.array( + [message.timestamp for message in self.state.initial_buffer] + ) + self.state.sample_rate = 1 / np.mean(np.diff(timestamps)) + logging.info(f"estimated sample rate = {self.state.sample_rate}Hz") + + self._init_and_run_filters() + return True + + def _init_and_run_filters(self): + # Initialize filters and run it for all but last sample in initial_buffer + if len(self.state.filters) != 0: + logging.warning( + "_init_filters called when self.state.filters is not empty, skipping!" + ) + return + # shape is [n_channel, ] + # initial state of each cascaded filter is calculated by taking + # the steady-state value of the previous filter in response to + # that previous filter's initial state! + initial_state = self.state.initial_buffer[0].sample + # initialize highpass filter + if ( + self.config.highpass_cutoff is not None + and self.config.highpass_order is not None + ): + self.state.filters.append( + CausalButterworth( + first_time_sample=initial_state, + cutoff=self.config.highpass_cutoff, + sample_freq=self.state.sample_rate, + btype="highpass", + order=self.config.highpass_order, + ) + ) + initial_state = self.state.filters[-1].process_sample(initial_state) + # initialize lowpass filters + if len(self.config.lowpass_cutoffs) > 0: + for cutoff in self.config.lowpass_cutoffs: + self.state.filters.append( + CausalButterworth( + first_time_sample=initial_state, + cutoff=cutoff, + sample_freq=self.state.sample_rate, + btype="lowpass", + order=1, + ) + ) + initial_state = self.state.filters[-1].process_sample(initial_state) + for buffered_message in self.state.initial_buffer[:-1]: + _ = self._process_sample(buffered_message.sample) + + def _process_sample(self, sample: np.ndarray) -> np.ndarray: + # Run process_sample through the entire cascade of filters + output = sample + for filt in self.state.filters: + output = filt.process_sample(output) + return output + + @lg.subscriber(INPUT) + @lg.publisher(OUTPUT) + async def run_filter(self, message: SignalSampleMessage) -> lg.AsyncPublisher: + """ + Assume message.sample is np.ndarray with dimension [n_channels, ] + """ + + if self.config.invert_input: + message = SignalSampleMessage(message.timestamp, -message.sample) + + if self.config.pass_through: + yield self.OUTPUT, message + return + + if not self._init_filter_class(message): + return + + filtered_sample = self._process_sample(message.sample) + """ + Return the filtered sample + filtered_sample now has to be reshaped to (channel, time) + because HDF5 file requires it that way + """ + sample_timestamp = ( + message.timestamp + if np.isnan(message.sample_timestamp) + else message.sample_timestamp + ) + yield self.OUTPUT, SignalSampleMessage( + timestamp=time.time(), + sample_timestamp=sample_timestamp, + sample=filtered_sample.reshape((-1, 1)), + ) diff --git a/signal_processing/nodes/faucet/logmean_node.py b/signal_processing/nodes/faucet/logmean_node.py new file mode 100644 index 000000000..e3fb60b6a --- /dev/null +++ b/signal_processing/nodes/faucet/logmean_node.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +""" +Node for performing logmean online +""" + +import time +from dataclasses import field +from typing import List, Optional + +import labgraph as lg +import numpy as np +from ...messages.generic_signal_sample import SignalSampleMessage + + +class LogMeanNodeConfig(lg.Config): + # When LogarithmNode is part of a bigger group, setting + # this to True provides an easy way to turn off + # this stage. + pass_through: bool = False + + +class LogMeanNodeState(lg.State): + # initialized to nan + last_nonzero_values: Optional[np.ndarray] = None + + +class LogmeanNode(lg.Node): + """ + The logic here follows closedly with that of `compute_log_all_samples`: + + Instead of taking the -log(x / mean(x)), we directly take the -log of a channel's + incoming signal value (assumed to be in units of Watt). + + If the incoming signal value is 0 or negative, this is assumed to be an anomaly, + either the sensor is bad (even noise has non-zero energy!) or turned off. + + In this case, we impute this value with the last non-zero signal value for this + channel. + + If the first value we receive for a channel is 0, then we return 0 for the + logmean result, and do not update the last nonzero-value for this channel. + """ + + # Subscribed topics + INPUT = lg.Topic(SignalSampleMessage) + + # Published topics + OUTPUT = lg.Topic(SignalSampleMessage) + + state: LogMeanNodeState + config: LogMeanNodeConfig + + @lg.subscriber(INPUT) + @lg.publisher(OUTPUT) + async def get_logmean(self, message: SignalSampleMessage) -> lg.AsyncPublisher: + if self.config.pass_through: + yield self.OUTPUT, message + else: + if self.state.last_nonzero_values is None: + self.state.last_nonzero_values = np.ones(message.sample.shape) * np.nan + + output = np.copy(message.sample) + + # Find 0 or negative channel values + is_less_eq_zero = output <= 0 + output[~is_less_eq_zero] = -np.log10(output[~is_less_eq_zero]) + # update the last_nonzero_values + self.state.last_nonzero_values[~is_less_eq_zero] = message.sample[ + ~is_less_eq_zero + ] + + # missing value imputation, no need to update state for these + can_impute = (~np.isnan(self.state.last_nonzero_values)) & is_less_eq_zero + output[can_impute] = -np.log10(self.state.last_nonzero_values[can_impute]) + + # deal with the non-imputable -- output will be 0, these are just bad channels! + cant_impute = np.isnan(self.state.last_nonzero_values) & is_less_eq_zero + output[cant_impute] = 0 + + yield self.OUTPUT, SignalSampleMessage( + timestamp=time.time(), + sample_timestamp=message.timestamp, + sample=output, + ) diff --git a/signal_processing/nodes/faucet/tests/__init__.py b/signal_processing/nodes/faucet/tests/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/nodes/faucet/tests/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/nodes/faucet/tests/test_logmean_node.py b/signal_processing/nodes/faucet/tests/test_logmean_node.py new file mode 100644 index 000000000..696cef164 --- /dev/null +++ b/signal_processing/nodes/faucet/tests/test_logmean_node.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import logging + +import labgraph as lg +import numpy as np +import pytest +from ..logmean_node import ( + LogmeanNode, + LogMeanNodeConfig, +) +from ....messages.generic_signal_sample import SignalSampleMessage + + +def build_test_signal( + n_time: int = 1000, + n_channels: int = 6, + power_scaler: float = 1e-10, +) -> np.ndarray: + if n_channels < 5: + logging.info("build_test_signal requires at least 5 channels, setting to 5") + n_channels = 5 + + signals = np.random.rand(n_time, n_channels) * power_scaler + # Set first channel to all 0 + signals[:, 0] = 0 + # Set second channel to all negative + signals[:, 1] = -signals[:, 1] + # Set third channel first sample to 0 + signals[0, 2] = 0 + # Set fourth channel first sample to negative + signals[0, 3] = -signals[0, 3] + # Set 10% of samples in signals[1:, 2:] to 0 or negative (5% and 5%) + indices = list(np.ndindex(signals[1:, 2:].shape)) # each index is offset by (1, 2) + np.random.shuffle(indices) # shuffle inplace + for (row, col) in indices[0 : len(indices) // 20]: + signals[row + 1, col + 2] = 0 + for (row, col) in indices[len(indices) // 20 : len(indices) // 10]: + signals[row + 1, col + 2] = -signals[row + 1, col + 2] + return signals + + +def get_correct_logmean_signal(signals: np.ndarray) -> np.ndarray: + output = np.copy(signals) + + for ch in range(signals.shape[1]): + sub_value = np.nan + for i in range(signals.shape[0]): + if signals[i, ch] > 0: + output[i, ch] = -np.log10(signals[i, ch]) + sub_value = signals[i, ch] + + elif signals[i, ch] <= 0: + output[i, ch] = 0 if np.isnan(sub_value) else -np.log10(sub_value) + + return output + + +def test_logmean_node_pass_through() -> None: + test_signals = build_test_signal() + timestamps = np.arange(len(test_signals)) * 0.1 + + test_harness = lg.NodeTestHarness(LogmeanNode) + messages = [ + SignalSampleMessage(timestamp=timestamps[i], sample=test_signals[i, :]) + for i in range(len(test_signals)) + ] + with test_harness.get_node(config=LogMeanNodeConfig(pass_through=True)) as node: + for i in range(0, len(messages)): + result = lg.run_async(node.get_logmean, args=(messages[i],)) + assert result is not None + np.testing.assert_array_equal(result[0][1].sample, test_signals[i, :]) + + +def test_logmean_node() -> None: + test_signals = build_test_signal() + correct_signals = get_correct_logmean_signal(test_signals) + timestamps = np.arange(len(test_signals)) * 0.1 + + test_harness = lg.NodeTestHarness(LogmeanNode) + messages = [ + SignalSampleMessage(timestamp=timestamps[i], sample=test_signals[i, :]) + for i in range(len(test_signals)) + ] + with test_harness.get_node(config=LogMeanNodeConfig(pass_through=False)) as node: + for i in range(0, len(messages)): + result = lg.run_async(node.get_logmean, args=(messages[i],)) + assert result is not None + # almost because log imprecision + np.testing.assert_array_almost_equal( + result[0][1].sample, correct_signals[i, :] + ) diff --git a/signal_processing/nodes/function_to_node.py b/signal_processing/nodes/function_to_node.py new file mode 100644 index 000000000..964c0a986 --- /dev/null +++ b/signal_processing/nodes/function_to_node.py @@ -0,0 +1,625 @@ +# Copyright 2004-present Facebook. All Rights Reserved. + +import asyncio +import inspect +from dataclasses import asdict, astuple, dataclass, field +from functools import wraps +from hashlib import sha1 +from inspect import Parameter, signature +from typing import Any, Callable, Dict, List, Optional, Set, Tuple + +import labgraph as lg +from toolz import curry +from ..utils.doc.docfill import fill_in_docstring + + +# The documentation for this function is very long; to make the function itself +# easier to read, the documention is moved here. +_argument_doc = fill_in_docstring( + { + "f": """f: Callable[[Any], Any] + The function to convert to a node. + """, + "base_cls": """base_cls: lg.Message = lg.Message + The class to inherit from for the ArgumentMessage and ReturnMessage. + All parameters inherited from this base class are passed through + directly to the output topic of the function node without alteration. + """, + "keyword_only_in_message": """keyword_only_in_message: bool = False, + Whether to include keyword only arguments in the message type. If + false, keyword only arguments can be passed only through the + `FunctionConfig` object; if true, the keyword arguments become fields + in the ArgumentMessage type. + """, + "default_keyword_in_message": """default_keyword_in_message: bool = False, + Whether to include arguments that have a default value in the + ArgumentMessage type. + """, + "deconstruct_dataclass_return_type": """deconstruct_dataclass_return_type: bool = True, + If a function returns a dataclass, setting this to true will make the + fields of the dataclass fields in the ReturnMessage class. + """, + "single_return_param_name": """single_return_param_name: str = "sample", + What to name the output message field parameter. If + `deconstruct_dataclass_return_type` is set to True, this parameter has + no effect. + """, + "argument_topic_name": """argument_topic_name: str = "INPUT", + Name of the input/argument topic in the FunctionNode. + """, + "return_topic_name": """return_topic_name: str = "OUTPUT", + Name of the output/return topic in the FunctionNode. + """, + "sleep_time": """sleep_time: float = 0.01, + If the function is a source (has no argument), how long should the + process wait before running the function again. + """, + "error": """error: Callable[[str], None] = lambda x: None, + The function to use to collect errors with. Multiple errors can be + captured during a function call. + """, + } +) + +############################################################################### +# Types # +############################################################################### + + +@dataclass +class FunctionToNode: + ArgumentMessage: Optional[lg.Message] + ReturnMessage: Optional[lg.Message] + FunctionNode: lg.Node + + def __iter__(self): + return iter(astuple(self)) + + +class FunctionConfig(lg.Config): + kwargs: Dict[str, Any] = field(default_factory=dict) + + +class _BlankMesage(lg.Message): + pass + + +############################################################################### +# Helper functions to determine function parameters # +############################################################################### + + +def is_keyword(arg): + return ( + arg.kind is Parameter.POSITIONAL_OR_KEYWORD + or arg.kind is Parameter.KEYWORD_ONLY + ) + + +def is_keyword_only(arg): + return arg.kind is Parameter.KEYWORD_ONLY + + +def is_nondefault_keyword(arg): + return is_keyword(arg) and arg.default is Parameter.empty + + +def is_default_keyword(arg): + return is_keyword(arg) and arg.default is not Parameter.empty + + +def positionally_passed(arg): + return (arg.kind is Parameter.POSITIONAL_ONLY) or ( + arg.kind is Parameter.POSITIONAL_OR_KEYWORD and arg.default is Parameter.empty + ) + + +def call_with_error(error_type): + """Collects a bunch of errors and returns them all once. + + Decorator that collects the errors in the decorated function so that the + user can see everything they need to fix at once. All errors are thrown + with the same error type. + + The decorated must have an `error` keyword parameter. The `error` parameter + is then ignored if the end user passes in that argument. + + Parameters + ---------- + error_type: type + The type of error to throw. For example, `ValueError`. + + Returns + ------- + Callable[Callable[[Any], Any], Callable[[Any], Any]] + Returns a decorator + + Example + ------- + >>> @call_with_error(ValueError) + >>> def func(a: int, b: int, error: Callable[[str], None]) -> int: + ... if a < 0: + ... error("a must be zero or greater") + ... if b < 0: + ... error("b must be zero or greater") + ... return a + b + + >>> func(-1, 0) + ValueError("a must be zero or greater") + + >>> func(0, -1) + ValueError("b must be zero or greater") + + >>> func(-1, -1) + ValueError("a must be zero or greater\nb must be zero or greater") + """ + + def _call_with_error(f): + @curry + def error(log, msg): + log.append(msg) + + @wraps(f) + def wrapped(*args, **kwargs): + log = [] + result = f(*args, error=error(log), **kwargs) + if len(log) > 0: + raise error_type("\n".join(log)) + return result + + return wrapped + + return _call_with_error + + +############################################################################### +# Determine function argument and return types # +############################################################################### + + +@_argument_doc +def parse_argument_annotations( + argument_annotations: Tuple[Parameter, ...], + function_name: str, + keyword_only_in_message: bool, + default_keyword_in_message: bool, + error: Callable[[str], None], +) -> Optional[Dict[str, type]]: + """Parse which argument_annotations should be placed into a Labgraph Message. + + The primary purpose of this function is to collect which parameters should + be passed to the final lg.Node through a lg.Message and which should be + passed through either default values or FunctionConfig. + + Parameters + ---------- + argument_annotations: Tuple[Parameter, ...] + The argument_annotations to a function, as Parameter objects. + function_name: str + The name of the function the argument_annotations came from. + {keyword_only_in_message} + {default_keyword_in_message} + {error} + + Returns + ------- + Optional[Dict[str, type]] + If no argument_annotations are provided, then None is returned. Else a + dictionary of argument names and types that are to go into a Labgraph + Message is returned. + """ + # Error handling components + var_err_msg = ( + f"Function `{function_name}` takes in a variable {{var_type}} parameter " + f"named `{{arg_name}}`. Functions with variable {{var_type}} parameters " + f"cannot be converted to Labgraph nodes using `function_to_node`." + ) + + # Functions with no argument_annotations are sources of data (type `() -> A`). + if len(argument_annotations) == 0: + return None + + argument_types = {} + for arg in argument_annotations: + # We do not support variable argument_annotations since we can't determine how + # many fields to make in the return Labgraph message. + if arg.kind is Parameter.VAR_POSITIONAL: + error(var_err_msg.format(var_type="argument", arg_name=arg.name)) + + if arg.kind is Parameter.VAR_KEYWORD: + error(var_err_msg.format(var_type="keyword argument", arg_name=arg.name)) + + if arg.annotation is Parameter.empty: + error( + f"Function `{function_name}` argument with name `{arg.name}` does not " + f"have a type annotation. All parameters require a type annotation to " + f"convert the function to a Labgraph node using `function_to_node`." + ) + + if positionally_passed(arg): + argument_types[arg.name] = arg.annotation + + elif default_keyword_in_message and is_default_keyword(arg): + # Python prevents us from having functions where one parameter is + # POSITIONAL_OR_KEYWORD with a default and another parameter that + # is KEYWORD_ONLY without a default. This means we can assume that + # default keyword arguments must be after non-default arguments. + # i.e the following is illegal. + # def f(a, b: bool = True, *, c:str) + argument_types[arg.name] = arg.annotation + + elif keyword_only_in_message and is_keyword_only(arg): + # We could potentially want default keywords to not be in the final + # message type and pass those in by default or through the + # `FunctionConfig` `kwargs` parameter. + if not default_keyword_in_message and is_default_keyword(arg): + pass + else: + argument_types[arg.name] = arg.annotation + + return argument_types + + +@_argument_doc +def parse_return_annotation( + return_annotation: type, + deconstruct_dataclass_return_type: bool, + single_return_param_name: str, +) -> Tuple[Optional[Dict[str, type]], bool]: + """Parse return annotation into something Labgraph can use. + + This function is primarily for unpacking return types that have an + __annotations__ property. We use this property to move the contents of the + return type directly into the final return lg.Message fields for + convenience and efficiency. + + Parameters + ---------- + return_annotation: type + The return type of a function + {deconstruct_dataclass_return_type} + {single_return_param_name} + + Returns + ------- + Tuple[Optional[Dict[str, type]], bool] + If no return type is provided, then (None, False) is returned. Else a + dictionary of the potentially deconstructed return names and types that + are to go into a Labgraph Message is returned, along with a bool of + whether the deconstruction occurred. + """ + # Base case of a sink function. + return_types = None + unpack_return = False + + # We have a function that is something besides a sink. + if return_annotation is not Parameter.empty: + + # We have a sink function (a function that returns nothing) + if return_annotation is None: + return_types = None + unpack_return = False + + # If the return type has an __annotations__ field we can attempt to + # unpack the results into the message directly. + elif deconstruct_dataclass_return_type and hasattr( + return_annotation, "__annotations__" + ): + return_types = { + name: typ for name, typ in return_annotation.__annotations__.items() + } + unpack_return = True + + # If the return type is anything else, do nothing with it but store it + # in the specified name. + else: + return_types = {single_return_param_name: return_annotation} + unpack_return = False + + return return_types, unpack_return + + +@call_with_error(ValueError) +def parse_function_annotations( + f: Callable[..., Any], + keyword_only_in_message: bool = False, + default_keyword_in_message: bool = False, + deconstruct_dataclass_return_type: bool = True, + single_return_param_name: str = "sample", + error: Callable[[str], None] = lambda x: None, +) -> Tuple[Optional[Dict[str, type]], Optional[Dict[str, type]], bool]: + """Convert function annotations into the fields to be turned into the + Labgraph Message argument and return types. + + Parameters + ---------- + {f} + {keyword_only_in_message} + {default_keyword_in_message} + {deconstruct_dataclass_return_type} + {single_return_param_name} + {error} + + Returns + ------- + Tuple[Optional[Dict[str, type]], Optional[Dict[str, type]], bool] + Dictionaries containing the argument types (if they exist), the return + types (if they exist), and a flag on if the return type was + deconstructed into its constituent pieces. + """ + + sig = signature(f) + name = f.__name__ + + argument_annotations = tuple(p for _, p in sig.parameters.items()) + return_annotation = sig.return_annotation + + # Returned parameters. Setting them all to the base case of no result. + argument_types = parse_argument_annotations( + argument_annotations, + name, + keyword_only_in_message, + default_keyword_in_message, + error, + ) + return_types, unpack_return = parse_return_annotation( + return_annotation, deconstruct_dataclass_return_type, single_return_param_name + ) + + if argument_types is None and return_types is None: + error( + f"Function `{name}` does not take or return any values. The function must " + f"either take a value, return a value, or take and return a value to be " + f"converted to a Labgraph node." + ) + + return (argument_types, return_types, unpack_return) + + +############################################################################### +# Message to dict (and reverse) conversions # +############################################################################### + + +def extract_message_fields(message: lg.Message, fields: List[str]) -> Dict[str, Any]: + return {field: getattr(message, field) for field in fields} + + +def remove_dict_keys(d: Dict[str, Any], fields: Set[str]) -> Dict[str, Any]: + return {name: value for name, value in d.items() if name not in fields} + + +def generate_message_class( + function_name: str, + message_class_name: str, + types: Optional[Dict[str, type]], + base_cls: type, +) -> type: + + # We do not generate a Message class when there are no types associated + # with either the arguments or return of a function. + if types is None: + return None + + # The `hash` function is not stable; it can return different results + # for the same input in different processes or on different platforms. + type_hash = sha1(repr(types.items()).encode("ascii")).hexdigest() + + # We put the hash in the message class name in order to prevent name + # clashes in cthulhu. + return type( + f"{message_class_name}_{function_name}_{type_hash}", + (base_cls,), + {"__annotations__": dict(types)}, + ) + + +############################################################################### +# Convert functions into classes # +############################################################################### + + +class _BlankMessage(lg.Message): + """This class is for passing someting to `call` in the source case. It + removes some branching logic that would reduce the readability of the + `call` function.""" + + pass + + +@_argument_doc +def function_to_node( + f: Callable[[Any], Any], + base_cls: lg.Message = lg.Message, + keyword_only_in_message: bool = False, + default_keyword_in_message: bool = False, + deconstruct_dataclass_return_type: bool = True, + single_return_param_name: str = "sample", + argument_topic_name: str = "INPUT", + return_topic_name: str = "OUTPUT", + sleep_time: float = 0.01, +) -> FunctionToNode: + """Convert a function to a labgraph node that operates on every message. + + For example, to lifting the following function: + + ``` + def f(a: int, b: int = 2) -> int: + return a * b * 4 + + ArgumentMessage, ReturnMessage, FunctionNode = function_to_node(f) + + # or + function_node = function_to_node(f) + ArgumentMessage = function_node.ArgumentMessage + ReturnMessage = function_node.ReturnMessage + FunctionNode = function_node.FunctionNode + ``` + + is the same as the following code + + ``` + def f(a: int, b: int = 2) -> int: + return a * b * 4 + + class ArgumentMessage(lg.Message): + sample: int + + class ReturnMessage(lg.Message): + sample: int + + class FunctionNode(lg.Node): + INPUT = lg.Topic(ArgumentMessage) + OUTPUT = lg.Topic(ReturnMessage) + + # keyword parameters can be passed by this config object, which takes a + # "kwargs" field with a dictionary value. + config: FunctionConfig + + @lg.subscriber(INPUT) + @lg.publisher(OUTPUT) + async def run(self, message: ArgumentMessage) -> lg.AsyncPublisher: + to_value = f(message.sample, **self.config.kwargs) + yield self.OUTPUT, ReturnMessage(sample=to_value) + ``` + + Note that if you would like to use the node in its own process (through the + `process_modules` method in a class), you MUST assign the results of this + function to the module namespace. This allows the returned classes to be + called from each process. + + Parameters + ---------- + {f} + {base_cls} + {keyword_only_in_message} + {default_keyword_in_message} + {deconstruct_dataclass_return_type} + {single_return_param_name} + {argument_topic_name} + {return_topic_name} + {sleep_time} + + Returns + ------- + FunctionToNode + Returns a type for the input Message, output Message, configuration + (keyword arguments) and Node that runs the function. + """ + function_name = f.__name__ + + argument_types, return_types, unpack_return = parse_function_annotations( + f, + keyword_only_in_message=keyword_only_in_message, + default_keyword_in_message=default_keyword_in_message, + deconstruct_dataclass_return_type=deconstruct_dataclass_return_type, + single_return_param_name=single_return_param_name, + ) + + # Generate message classes and topics ############################ + + base_cls_fields = base_cls.__message_fields__ + + ArgumentMessage = generate_message_class( + function_name, "ArgumentMessage", argument_types, base_cls + ) + ReturnMessage = generate_message_class( + function_name, "ReturnMessage", return_types, base_cls + ) + + topics = {} + + if argument_types is not None: + argument_topic = lg.Topic(ArgumentMessage) + topics[argument_topic_name] = argument_topic + + if return_types is not None: + return_topic = lg.Topic(ReturnMessage) + topics[return_topic_name] = return_topic + + # Generic method to call the provided function ################### + + def call(message, kwargs): + # Any fields from the base message class is passed directly through to + # the output message. For example, if the base class is + # `lg.TimestampedMessage`, then the field `timestamp` is passed through + # to the output message directly. + pass_through_fields = extract_message_fields(message, base_cls_fields) + + # If we have default keyword allowed but not keyword arguments, we run into + # a case where the order of argument types skips over an argument. This means + # that positional only arguments are not called correctly. Since we are not + # using Python 3.8 for a while, we are going to call everything by keyword + # instead. + message_fields = remove_dict_keys(message.asdict(), base_cls_fields) + if len(kwargs) > 0: + function_result = f(**{**message_fields, **kwargs}) + else: + function_result = f(**message_fields) + + if unpack_return: + result_dict = asdict(function_result) + else: + result_dict = {single_return_param_name: function_result} + + if ReturnMessage is not None: + return ReturnMessage(**{**pass_through_fields, **result_dict}) + + # Generate node function linked to topics for source, ############ + # sink, and pipe-through function cases ############ + + if argument_types is None and return_types is not None: + + # Case where function takes in nothing and produces some output (a + # source function). + @lg.publisher(return_topic) + async def run(self) -> lg.AsyncPublisher: + # We need a loop here of some sort, otherwise the node will only + # run once. + while True: + output_msg = call(_BlankMessage(), self.config.kwargs) + yield getattr(self, return_topic_name), output_msg + await asyncio.sleep(sleep_time) + + if argument_types is not None and return_types is None: + + # Case where function takes an input and produces no output (a sink + # function). + @lg.subscriber(argument_topic) + async def run(self, message: ArgumentMessage): # noqa: F811 + call(message, self.config.kwargs) + + if argument_types is not None and return_types is not None: + + # Case where a function both takes an input and produces an output (the + # pipe-through case) + @lg.subscriber(argument_topic) + @lg.publisher(return_topic) + async def run( # noqa: F811 + self, message: ArgumentMessage + ) -> lg.AsyncPublisher: + output_msg = call(message, self.config.kwargs) + yield getattr(self, return_topic_name), output_msg + + # Generate node to call function from prior pieces ############### + + # We provide a __module__ name for the case when the function is called in + # the top-level module namespace. This allows the node generated from this + # class to be used as a member of `process_modules` method in a graph. + caller_frame = inspect.stack()[1] + caller_module_name = inspect.getmodule(caller_frame[0]).__name__ + + FunctionNode = type( + f"FunctionNode_{function_name}", + (lg.Node,), + { + **topics, + **{ + "__annotations__": {"config": FunctionConfig}, + "__module__": caller_module_name, + "run": run, + }, + }, + ) + + return FunctionToNode(ArgumentMessage, ReturnMessage, FunctionNode) diff --git a/signal_processing/nodes/tests/__init__.py b/signal_processing/nodes/tests/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/nodes/tests/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/nodes/tests/test_function_to_node.py b/signal_processing/nodes/tests/test_function_to_node.py new file mode 100644 index 000000000..0e996ec5c --- /dev/null +++ b/signal_processing/nodes/tests/test_function_to_node.py @@ -0,0 +1,650 @@ +# Copyright 2004-present Facebook. All Rights Reserved. + +from dataclasses import dataclass +from textwrap import dedent +from typing import Callable, Optional, Tuple + +import labgraph as lg +import pytest +from ..function_to_node import ( + FunctionConfig, + function_to_node, + parse_function_annotations, +) + + +############################################################################### +# Test parse_function_annotations bad function error cases # +############################################################################### + + +def test_var_args_raises() -> None: + def f(a: int, *args): + pass + + with pytest.raises(ValueError): + parse_function_annotations(f) + + +def test_var_kwargs_raises() -> None: + def f(a: int, **kwargs): + pass + + with pytest.raises(ValueError): + parse_function_annotations(f) + + +def test_no_function_annotation_raises() -> None: + def f(a): + pass + + with pytest.raises(ValueError): + parse_function_annotations(f) + + +def test_empty_function_raises() -> None: + def f(): + pass + + with pytest.raises(ValueError): + parse_function_annotations(f) + + +def test_multiple_errors_returned_at_once() -> None: + def f(a, *args, **kwargs): + pass + + err_msg = dedent( + """ + Function `f` argument with name `a` does not have a type annotation. All parameters require a type annotation to convert the function to a Labgraph node using `function_to_node`. + Function `f` takes in a variable argument parameter named `args`. Functions with variable argument parameters cannot be converted to Labgraph nodes using `function_to_node`. + Function `f` argument with name `args` does not have a type annotation. All parameters require a type annotation to convert the function to a Labgraph node using `function_to_node`. + Function `f` takes in a variable keyword argument parameter named `kwargs`. Functions with variable keyword argument parameters cannot be converted to Labgraph nodes using `function_to_node`. + Function `f` argument with name `kwargs` does not have a type annotation. All parameters require a type annotation to convert the function to a Labgraph node using `function_to_node`.""" + ).strip() + + with pytest.raises(ValueError) as excinfo: + parse_function_annotations(f) + assert err_msg == str(excinfo.value) + + +############################################################################### +# Test parse_function_annotations gives correct output # +############################################################################### + + +def test_no_input_or_output() -> None: + def f(): + pass + + with pytest.raises(ValueError): + parse_function_annotations(f) + + +def test_source() -> None: + def f() -> str: + pass + + assert parse_function_annotations(f) == (None, {"sample": str}, False) + + +def test_sink() -> None: + def f(a: int) -> None: + pass + + assert parse_function_annotations(f) == ({"a": int}, None, False) + + +def test_arity_1() -> None: + def f(a: int) -> str: + pass + + assert parse_function_annotations(f) == ({"a": int}, {"sample": str}, False) + + +def test_arity_2() -> None: + def f(a: int, b: bool) -> str: + pass + + assert parse_function_annotations(f) == ( + {"a": int, "b": bool}, + {"sample": str}, + False, + ) + + +def test_keyword_only_in_message() -> None: + def f(a: int, *, b: bool) -> str: + pass + + assert parse_function_annotations(f, keyword_only_in_message=False) == ( + {"a": int}, + {"sample": str}, + False, + ) + assert parse_function_annotations(f, keyword_only_in_message=True) == ( + {"a": int, "b": bool}, + {"sample": str}, + False, + ) + + +def test_default_keyword_in_message() -> None: + def f(a: int, b: bool = True) -> str: + pass + + assert parse_function_annotations(f, default_keyword_in_message=False) == ( + {"a": int}, + {"sample": str}, + False, + ) + assert parse_function_annotations(f, default_keyword_in_message=True) == ( + {"a": int, "b": bool}, + {"sample": str}, + False, + ) + + +def test_default_keyword_in_message_and_keyword_only_in_message() -> None: + def f(a: int, *, b: bool, c: float = 0.0) -> str: + pass + + assert parse_function_annotations( + f, default_keyword_in_message=False, keyword_only_in_message=False + ) == ({"a": int}, {"sample": str}, False) + assert parse_function_annotations( + f, default_keyword_in_message=False, keyword_only_in_message=True + ) == ({"a": int, "b": bool}, {"sample": str}, False) + assert parse_function_annotations( + f, default_keyword_in_message=True, keyword_only_in_message=False + ) == ({"a": int, "c": float}, {"sample": str}, False) + assert parse_function_annotations( + f, default_keyword_in_message=True, keyword_only_in_message=True + ) == ({"a": int, "b": bool, "c": float}, {"sample": str}, False) + + +def test_deconstruct_dataclass_return_type() -> None: + @dataclass + class A: + field1: int + field2: str + + def f(a: int) -> A: + pass + + assert parse_function_annotations(f, deconstruct_dataclass_return_type=False) == ( + {"a": int}, + {"sample": A}, + False, + ) + assert parse_function_annotations(f, deconstruct_dataclass_return_type=True) == ( + {"a": int}, + {"field1": int, "field2": str}, + True, + ) + + +def test_single_return_param_name() -> None: + def f(a: int) -> str: + pass + + assert parse_function_annotations( + f, single_return_param_name="zhu_li_do_the_thing" + ) == ({"a": int}, {"zhu_li_do_the_thing": str}, False) + + +############################################################################### +# Test function_to_node messages match function # +############################################################################### + + +def test_no_input_message() -> None: + def f() -> str: + pass + + (ArgumentMessage, ReturnMessage, _) = function_to_node(f) + + assert ArgumentMessage is None + + # Can we make an instance of the output message? + assert ReturnMessage.__annotations__ == {"sample": str} + ReturnMessage(sample="hello") + + +def test_no_output_message() -> None: + def f(a: int): + pass + + (ArgumentMessage, ReturnMessage, _) = function_to_node(f) + + # Can we make an instance of the input message? + assert ArgumentMessage.__annotations__ == {"a": int} + ArgumentMessage(a=0) + + assert ReturnMessage is None + + +def test_message_arity_1() -> None: + def f(a: int) -> str: + pass + + (ArgumentMessage, ReturnMessage, _) = function_to_node(f) + + assert ArgumentMessage.__annotations__ == {"a": int} + ArgumentMessage(a=0) + + assert ReturnMessage.__annotations__ == {"sample": str} + ReturnMessage(sample="hello") + + +def test_message_keyword_only_in_message() -> None: + def f(a: int, *, b: bool) -> str: + pass + + # False case + (ArgumentMessage, ReturnMessage, _) = function_to_node( + f, keyword_only_in_message=False + ) + assert ArgumentMessage.__annotations__ == {"a": int} + ArgumentMessage(a=1) + + assert ReturnMessage.__annotations__ == {"sample": str} + ReturnMessage(sample="Hello") + + # True case + (ArgumentMessage, ReturnMessage, _) = function_to_node( + f, keyword_only_in_message=True + ) + assert ArgumentMessage.__annotations__ == {"a": int, "b": bool} + ArgumentMessage(a=1, b=False) + + assert ReturnMessage.__annotations__ == {"sample": str} + ReturnMessage(sample="Hello") + + +def test_message_default_keyword_in_message() -> None: + def f(a: int, b: bool = True) -> str: + pass + + # False case + (ArgumentMessage, ReturnMessage, _) = function_to_node( + f, default_keyword_in_message=False + ) + assert ArgumentMessage.__annotations__ == {"a": int} + ArgumentMessage(a=1) + + assert ReturnMessage.__annotations__ == {"sample": str} + ReturnMessage(sample="Hello") + + # True case + (ArgumentMessage, ReturnMessage, _) = function_to_node( + f, default_keyword_in_message=True + ) + assert ArgumentMessage.__annotations__ == {"a": int, "b": bool} + ArgumentMessage(a=1, b=False) + + assert ReturnMessage.__annotations__ == {"sample": str} + ReturnMessage(sample="Hello") + + +def test_message_default_keyword_in_message_and_keyword_only_in_message() -> None: + def f(a: int, *, b: bool, c: float = 0.0) -> str: + pass + + # False, False case + (ArgumentMessage, ReturnMessage, _) = function_to_node( + f, default_keyword_in_message=False, keyword_only_in_message=False + ) + assert ArgumentMessage.__annotations__ == {"a": int} + ArgumentMessage(a=1) + + assert ReturnMessage.__annotations__ == {"sample": str} + ReturnMessage(sample="Hello") + + # False, True case + (ArgumentMessage, ReturnMessage, _) = function_to_node( + f, default_keyword_in_message=False, keyword_only_in_message=True + ) + assert ArgumentMessage.__annotations__ == {"a": int, "b": bool} + ArgumentMessage(a=1, b=False) + + assert ReturnMessage.__annotations__ == {"sample": str} + ReturnMessage(sample="Hello") + + # True, False case + (ArgumentMessage, ReturnMessage, _) = function_to_node( + f, default_keyword_in_message=True, keyword_only_in_message=False + ) + assert ArgumentMessage.__annotations__ == {"a": int, "c": float} + ArgumentMessage(a=1, c=0.0) + + assert ReturnMessage.__annotations__ == {"sample": str} + ReturnMessage(sample="Hello") + + # True, True case + (ArgumentMessage, ReturnMessage, _) = function_to_node( + f, default_keyword_in_message=True, keyword_only_in_message=True + ) + assert ArgumentMessage.__annotations__ == {"a": int, "b": bool, "c": float} + ArgumentMessage(a=1, b=False, c=0.0) + + assert ReturnMessage.__annotations__ == {"sample": str} + ReturnMessage(sample="Hello") + + +# If this is included inside of the function it cannot be pickled. +@dataclass +class A: + field1: int + field2: str + + +def test_output_message_unpacking() -> None: + def f(a: int) -> A: + pass + + (_, ReturnMessage, _) = function_to_node(f, deconstruct_dataclass_return_type=False) + assert ReturnMessage.__annotations__ == {"sample": A} + ReturnMessage(sample=A(1, "Hello")) + + (_, ReturnMessage, _) = function_to_node(f, deconstruct_dataclass_return_type=True) + assert ReturnMessage.__annotations__ == {"field1": int, "field2": str} + ReturnMessage(field1=1, field2="Hello") + + +def test_message_single_return_param_name() -> None: + def f(a: int) -> str: + pass + + (_, ReturnMessage, _) = function_to_node( + f, single_return_param_name="zhu_li_do_the_thing" + ) + + assert ReturnMessage.__annotations__ == {"zhu_li_do_the_thing": str} + ReturnMessage(zhu_li_do_the_thing="alright_varrick") + + +def test_topic_names() -> None: + def f(a: int) -> str: + pass + + input_topic_name = "hello" + output_topic_name = "goodbye" + + (ArgumentMessage, ReturnMessage, FunctionNode) = function_to_node( + f, argument_topic_name=input_topic_name, return_topic_name=output_topic_name + ) + + input_topic = getattr(FunctionNode, input_topic_name) + isinstance(input_topic, lg.Topic) + assert input_topic.message_type is ArgumentMessage + + output_topic = getattr(FunctionNode, output_topic_name) + isinstance(output_topic, lg.Topic) + assert output_topic.message_type is ReturnMessage + + +############################################################################### +# Test that FunctionNode has correct properties # +############################################################################### + + +def test_function_node_has_required_entities() -> None: + def f(a: int) -> str: + pass + + (ArgumentMessage, ReturnMessage, FunctionNode) = function_to_node(f) + + input_topic = FunctionNode.INPUT + isinstance(input_topic, lg.Topic) + assert input_topic.message_type is ArgumentMessage + + output_topic = FunctionNode.OUTPUT + isinstance(output_topic, lg.Topic) + assert output_topic.message_type is ReturnMessage + + assert FunctionNode.__module__ == __name__ + + assert callable(FunctionNode.run) + + assert FunctionNode.__annotations__ == {"config": FunctionConfig} + + +############################################################################### +# Test function_to_node(f).run(x) == f(x) # +############################################################################### + + +# We generate a class here to not rely on external changes. Additionally we +# layer the classes to demonstrate that the layers of fields are passed through +# correctly. +class SampledIndexMessage(lg.Message): + sample_index: int + + +class SampledFlavorMessage(SampledIndexMessage): + sample_flavor: str + + +def test_function_returns_same_after_conversion() -> None: + """Besides testing that the conversion doesn't change the function, it also + tests that a base class that is not `lg.Message` has its parameters passed + through.""" + + def f(a: int, *, b: int) -> int: + return a + b + + # Get the autogenerated node and associated classes + ArgumentMessage, ReturnMessage, FunctionNode = function_to_node( + f, base_cls=SampledFlavorMessage + ) + + test_harness = lg.NodeTestHarness(FunctionNode) + + # Check that the output from the node matches the output from the function. + a = 2 + b = 3 + flavor = "bubblegum" + + with test_harness.get_node(config=FunctionConfig(kwargs={"b": b})) as node: + msg = ArgumentMessage(sample_index=0, sample_flavor=flavor, a=a) + node_to_value_msg = lg.run_async(node.run, args=[msg])[0][1] + + assert isinstance(node_to_value_msg, ReturnMessage) + assert node_to_value_msg.sample == f(a, b=b) + assert node_to_value_msg.sample_index == 0 + assert node_to_value_msg.sample_flavor == flavor + + +def test_function_config_overrides_message(): + def f(a: int, *, b: int) -> int: + return a + b + + # Get the autogenerated node and associated classes + ArgumentMessage, ReturnMessage, FunctionNode = function_to_node( + f, keyword_only_in_message=True + ) + + test_harness = lg.NodeTestHarness(FunctionNode) + + # Check that the output from the node matches the output from the function. + a = 2 + b_message = 3 + b_config = 4 + + with test_harness.get_node(config=FunctionConfig(kwargs={"b": b_config})) as node: + msg = ArgumentMessage(a=a, b=b_message) + node_to_value_msg = lg.run_async(node.run, args=[msg])[0][1] + + assert isinstance(node_to_value_msg, ReturnMessage) + assert node_to_value_msg.sample == f(a, b=b_config) + + +############################################################################### +# Run in a graph # +############################################################################### + +# Source function ############################################################# + + +def generate_source_function() -> Callable[[], int]: + counter = 0 + + def source() -> int: + nonlocal counter + counter += 1 + return counter + + return source + + +_, SourceReturnMessage, SourceFunctionNode = function_to_node( + generate_source_function(), sleep_time=0.03 +) + + +class SourceConsumer(lg.Node): + INPUT = lg.Topic(SourceReturnMessage) + + last_count: Optional[int] + + def setup(self): + self.last_count = None + + @lg.subscriber(INPUT) + async def run(self, message: SourceReturnMessage): + if self.last_count is None: + self.last_count = message.sample - 1 + assert (message.sample - self.last_count) == 1 + + self.last_count = message.sample + if message.sample > 10: + raise lg.NormalTermination() + + +class SourceGraph(lg.Graph): + FUNCTION: SourceFunctionNode + CONSUMER: SourceConsumer + + def connections(self) -> lg.Connections: + return ((self.FUNCTION.OUTPUT, self.CONSUMER.INPUT),) + + def process_modules(self) -> Tuple[lg.Module, ...]: + return (self.FUNCTION, self.CONSUMER) + + +@pytest.mark.skip(reason="Currently complains of hanging source node") +def test_source_graph() -> None: + g = SourceGraph() + runner = lg.ParallelRunner(graph=g) + runner.run() + + +# Sink function ############################################################### + + +def sink(a: int): + pass + + +SinkArgumentMessage, _, SinkFunctionNode = function_to_node(sink) + + +class SinkGenerator(lg.Node): + OUTPUT = lg.Topic(SinkArgumentMessage) + + counter: int + + def setup(self): + self.counter = 0 + + @lg.publisher(OUTPUT) + async def run(self) -> lg.AsyncPublisher: + while True: + self.counter += 1 + if self.counter > 10: + raise lg.NormalTermination() + + yield self.OUTPUT, SinkArgumentMessage(a=self.counter) + + +class SinkGraph(lg.Graph): + GENERATOR: SinkGenerator + FUNCTION: SinkFunctionNode + + def connections(self) -> lg.Connections: + return ((self.GENERATOR.OUTPUT, self.FUNCTION.INPUT),) + + def process_modules(self) -> Tuple[lg.Module, ...]: + return (self.GENERATOR, self.FUNCTION) + + +def test_sink_graph() -> None: + g = SinkGraph() + runner = lg.ParallelRunner(graph=g) + runner.run() + + +# Function that takes and returns a value ##################################### + + +def input_output_function(a: int) -> int: + return a + 4 + + +( + InputOutputArgumentMessage, + InputOutputReturnMessage, + InputOutputFunctionNode, +) = function_to_node(input_output_function) + + +class InputOutputGenerator(lg.Node): + OUTPUT = lg.Topic(InputOutputArgumentMessage) + + counter: int + + def setup(self): + self.counter = 0 + + @lg.publisher(OUTPUT) + async def run(self) -> lg.AsyncPublisher: + while self.counter < 10: + self.counter += 1 + yield self.OUTPUT, InputOutputArgumentMessage(a=self.counter) + raise lg.NormalTermination() + + +class InputOutputConsumer(lg.Node): + INPUT = lg.Topic(InputOutputReturnMessage) + + count: int + + def setup(self): + self.count = 0 + + @lg.subscriber(INPUT) + async def run(self, message: InputOutputReturnMessage): + self.count += 1 + assert message.sample == input_output_function(self.count) + + +class InputOutputGraph(lg.Graph): + GENERATOR: InputOutputGenerator + FUNCTION: InputOutputFunctionNode + CONSUMER: InputOutputConsumer + + def connections(self) -> lg.Connections: + return ( + (self.GENERATOR.OUTPUT, self.FUNCTION.INPUT), + (self.FUNCTION.OUTPUT, self.CONSUMER.INPUT), + ) + + def process_modules(self) -> Tuple[lg.Module, ...]: + return (self.GENERATOR, self.FUNCTION, self.CONSUMER) + + +def test_input_output_graph() -> None: + g = InputOutputGraph() + runner = lg.ParallelRunner(graph=g) + runner.run() diff --git a/signal_processing/nodes/tests/test_window_trigger.py b/signal_processing/nodes/tests/test_window_trigger.py new file mode 100644 index 000000000..dc4312b22 --- /dev/null +++ b/signal_processing/nodes/tests/test_window_trigger.py @@ -0,0 +1,144 @@ +# Copyright 2004-present Facebook. All Rights Reserved. + +from typing import List, Optional + +import labgraph as lg +from ..window_trigger import ( + make_window_trigger_node, + WindowTriggerConfig, +) + + +############################################################################### +# Types used across tests # +############################################################################### + + +class SampleMessage(lg.TimestampedMessage): + sample: int + + def __eq__(self, other: "SampleMessage"): + return (self.timestamp == other.timestamp) and (self.sample == other.sample) + + +############################################################################### +# Parameters used across tests # +############################################################################### + + +def trigger_on_index(index: int) -> bool: + return index % 2 == 0 + + +def trigger(messages: List[SampleMessage]) -> bool: + return trigger_on_index(messages[0].sample) + + +############################################################################### +# Test returned window node matches expected state machine # +############################################################################### + + +class TestMakeWindowTriggerNode: + + StreamState = Optional[List[lg.TimestampedMessage]] + + length = 2 + messages_to_send = 10 + + def predict_state(self, timestamp: float, index: int) -> StreamState: + if trigger_on_index(index - self.length + 1): + return [ + SampleMessage(timestamp=timestamp - k, sample=index - k) + for k in range(self.length) + ][::-1] + + return None + + def convert_run_async_message_to_stream_state(self, window_message) -> StreamState: + if len(window_message) == 0: + return None + + return window_message[0][1].sample + + def test_function_returns_same_after_lift(self) -> None: + WindowTriggerMessage, WindowTriggerNode = make_window_trigger_node( + SampleMessage, trigger + ) + + test_harness = lg.NodeTestHarness(WindowTriggerNode) + config = WindowTriggerConfig(self.length) + + with test_harness.get_node(config=config) as node: + for k in range(self.messages_to_send): + msg = SampleMessage(timestamp=float(k), sample=k) + + predicted_state = self.predict_state(msg.timestamp, k) + state = self.convert_run_async_message_to_stream_state( + lg.run_async(node.run, args=[msg]) + ) + + assert predicted_state == state + + +############################################################################### +# Run in a graph # +############################################################################### + +# # Needs to be at a top level in order to be run through an actual graph +length = 2 +messages_to_send = 10 + +WindowTriggerMessage, WindowTriggerNode = make_window_trigger_node( + SampleMessage, trigger +) + + +class Generator(lg.Node): + OUTPUT = lg.Topic(SampleMessage) + + counter: int + + def setup(self): + self.counter = 0 + + @lg.publisher(OUTPUT) + async def run(self) -> lg.AsyncPublisher: + while self.counter < messages_to_send: + self.counter += 1 + yield self.OUTPUT, SampleMessage( + timestamp=float(self.counter), sample=self.counter + ) + raise lg.NormalTermination() + + +class Consumer(lg.Node): + INPUT = lg.Topic(WindowTriggerMessage) + + @lg.subscriber(INPUT) + async def run(self, messages: WindowTriggerMessage): + assert trigger(messages) + + +class Graph(lg.Graph): + GENERATOR: Generator + WINDOW_TRIGGER: WindowTriggerNode + CONSUMER: Consumer + + config: WindowTriggerConfig + + def setup(self) -> None: + self.WINDOW_TRIGGER.configure(self.config) + + def connections(self) -> lg.Connections: + return ( + (self.GENERATOR.OUTPUT, self.WINDOW_TRIGGER.INPUT), + (self.WINDOW_TRIGGER.OUTPUT, self.CONSUMER.INPUT), + ) + + +def test_in_graph() -> None: + g = Graph() + g.configure(WindowTriggerConfig(2)) + runner = lg.ParallelRunner(graph=g) + runner.run() diff --git a/signal_processing/nodes/window_trigger.py b/signal_processing/nodes/window_trigger.py new file mode 100644 index 000000000..111b0ed62 --- /dev/null +++ b/signal_processing/nodes/window_trigger.py @@ -0,0 +1,158 @@ +# Copyright 2004-present Facebook. All Rights Reserved. + +from collections import deque +from dataclasses import astuple, dataclass +from typing import Callable, List, Optional, Type + +import labgraph as lg + + +TriggerWindowPredicate = Callable[[List[lg.TimestampedMessage]], bool] + + +@dataclass +class WindowTriggerMessageAndNode: + message: lg.TimestampedMessage + node: lg.Node + + def __iter__(self): + return iter(astuple(self)) + + +class WindowTriggerConfig(lg.Config): + """Config for the node class produced by `make_window_trigger_node()` + + Members + ---------- + length: int + How many messages should be in a window. + """ + + length: int + + +@dataclass +class _WindowEmitter: + """ + A stateful object that keeps track of `n` samples from a stream, and + returns the `n` samples if some trigger predicate returns true for the + current set of samples. + """ + + # Window lengths are in number of messages, since the time stamps for the + # messages can vary and lead to uneven window sizes. + length: int + trigger_window_predicate: TriggerWindowPredicate + + def __post_init__(self): + self._window = deque(maxlen=self.length) + + def __update_window(self, message: lg.TimestampedMessage): + self._window.append(message) + + @property + def __window_is_full(self): + return len(self._window) >= self.length + + def __call__( + self, message: lg.TimestampedMessage + ) -> Optional[List[lg.TimestampedMessage]]: + self.__update_window(message) + if self.__window_is_full: + if self.trigger_window_predicate(self._window): + return list(self._window) + return None + + +def make_window_trigger_message(message_type: Type[lg.TimestampedMessage]): + class WindowTriggerMessage(lg.TimestampedMessage): + sample: List[message_type] + + return WindowTriggerMessage + + +def make_window_trigger_node( + message_type: Type[lg.TimestampedMessage], + trigger_window_predicate: TriggerWindowPredicate, +) -> WindowTriggerMessageAndNode: + """Generates a node that windows from a stream if some trigger fires. + + Parameters + ---------- + message_type: type + The type of the message for the stream to window/buffer + trigger_window_predicate: TriggerWindowPredicate + Whether to send out the current window to a stream or not. + + Returns + ------- + WindowTriggerMessageAndNode + A dataclass with a `message` field for the type of the output message + from the node, and `node` field that holds the window triggering node. + + Example + ------- + + Say you are streaming simple messages and want to capture every 10 samples + where the first sample is zero. First, define the messages that are going + to be streamed. + + >>> import labgraph as lg + >>> class SampleMessage(lg.TimestampedMessage): + ... field: int + + Now define the number of samples that are in a window + + >>> n_samples = 10 + + Along with a trigger function, which determines when a list of those + messages is a window that you want. We specified that we wanted the buffer + to have the `SampleMessage` field be zero as the first message in the + window, which can be presented as the following function. + + >>> def trigger(messages: List[SampleMessage]) -> bool: + ... return messages[0].field == 0 + >>> + + Finally, pass the window length and trigger function to the + `make_window_trigger_node` function to generate a Labgraph node that will + buffer a stream and return windows into that stream that cause the trigger + function to return True. + + >>> message_and_node = make_window_trigger_node(n_samples, trigger) + + Now just unpack the result as you would like. If you want to use in a + graph, you will likely need to assign the results of + `make_window_trigger_node` to the top level of a module for Labgraph to be + able to pick up the classes/types correctly. + + >>> WindowTriggerNode = message_and_node.node + >>> WindowTriggerMessage = message_and_node.message + """ + + WindowTriggerMessage = make_window_trigger_message(message_type) + + class WindowTriggerNode(lg.Node): + INPUT = lg.Topic(message_type) + OUTPUT = lg.Topic(WindowTriggerMessage) + + window_emitter: _WindowEmitter + + config: WindowTriggerConfig + + def setup(self) -> None: + self.window_emitter = _WindowEmitter( + self.config.length, trigger_window_predicate + ) + + @lg.subscriber(INPUT) + @lg.publisher(OUTPUT) + async def run(self, message: message_type) -> lg.AsyncPublisher: + optional_window = self.window_emitter(message) + + if optional_window is not None: + yield self.OUTPUT, WindowTriggerMessage( + timestamp=message.timestamp, sample=optional_window + ) + + return WindowTriggerMessageAndNode(WindowTriggerMessage, WindowTriggerNode) diff --git a/signal_processing/synthetic/__init__.py b/signal_processing/synthetic/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/synthetic/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/synthetic/generators/__init__.py b/signal_processing/synthetic/generators/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/synthetic/generators/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/synthetic/generators/noise_generator.py b/signal_processing/synthetic/generators/noise_generator.py new file mode 100644 index 000000000..affa90ccc --- /dev/null +++ b/signal_processing/synthetic/generators/noise_generator.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from dataclasses import dataclass + +import numpy as np +from labgraph.simulations import ( + FunctionChannelConfig, + FunctionGenerator, + FunctionGeneratorMessage, +) + + +@dataclass +class NoiseChannelConfig(FunctionChannelConfig): + """ + Configuration variables describing the samples produced by this generator. + Noise is generated + Args: + - amplitudes: The amplitude of the noise + - offsets: the offset of the noise + - sample_rate: the sample rate. + """ + + amplitudes: np.ndarray + offsets: np.ndarray + sample_rate: float + + def __post_init__(self) -> None: + assert self.shape == self.amplitudes.shape == self.offsets.shape + + +class NoiseGenerator(FunctionGenerator): + """ + Generator which produces a uniformly distributed random noise on each channel. + Ammplitude and offset of the noise is controlled by the configuration. + """ + + def __init__(self, config: NoiseChannelConfig) -> None: + """Passing the config in __init__ seems easier than separately calling + set_config on the object afterwards. Also means I can avoid the + check for a valid config in the next_sample function + """ + super().__init__() + self.set_channel_config(config) + self._time = 0.0 + + def next_sample(self) -> np.ndarray: + sample = ( + self.channel_config.amplitudes * np.random.random(self.channel_config.shape) + + self.channel_config.offsets + ) + sample_message = FunctionGeneratorMessage(self._time, sample) + # Update time + self._time += 1 / self.channel_config.sample_rate + return sample_message diff --git a/signal_processing/synthetic/generators/sine_wave_generator.py b/signal_processing/synthetic/generators/sine_wave_generator.py new file mode 100644 index 000000000..66b55aefc --- /dev/null +++ b/signal_processing/synthetic/generators/sine_wave_generator.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from dataclasses import dataclass + +import numpy as np +from labgraph.simulations import ( + FunctionChannelConfig, + FunctionGenerator, + FunctionGeneratorMessage, +) + + +@dataclass +class SineWaveChannelConfig(FunctionChannelConfig): + """ + Configuration variables describing the samples produced by this generator. + + Args: + - amplitudes: The amplitude of the sinusoid. + - frequencies: The frequency of the sinusoid. + - phase_shifts: The phase shift for this sinusoid, in rads. + - midlines: The midline of the sinusoid. + - sample_rate: the sample rate. + """ + + amplitudes: np.ndarray + frequencies: np.ndarray + phase_shifts: np.ndarray + midlines: np.ndarray + sample_rate: float + + def __post_init__(self) -> None: + assert ( + self.amplitudes.shape + == self.frequencies.shape + == self.phase_shifts.shape + == self.midlines.shape + ) + assert self.sample_rate > 0.0 + + +class SineWaveGenerator(FunctionGenerator): + """ + Generator which produces a continuous sinusoid on each channel at the amplitude, + frequency, phase shift, and midline specified by each channel's + SineWaveChannelConfig object in this generator's config. + """ + + def __init__(self, config: SineWaveChannelConfig) -> None: + """Passing the config in __init__ seems easier than separately calling + set_config on the object afterwards. Also means I can avoid the + check for a valid config in the next_sample function + """ + super().__init__() + self.set_channel_config(config) + # This is time, which starts at zero + self._time = np.zeros(self.channel_config.shape) + + def next_sample(self) -> FunctionGeneratorMessage: + # Calculate output of the set of sine waves + angles = self.channel_config.frequencies * 2 * np.pi * self._time + angles = angles + self.channel_config.phase_shifts + sample = ( + self.channel_config.amplitudes * np.sin(angles) + + self.channel_config.midlines + ) + sample_message = FunctionGeneratorMessage(self._time.flatten()[0], sample) + # Update time + self._time += 1 / self.channel_config.sample_rate + return sample_message diff --git a/signal_processing/synthetic/generators/tests/__init__.py b/signal_processing/synthetic/generators/tests/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/synthetic/generators/tests/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/synthetic/generators/tests/test_noise_generator.py b/signal_processing/synthetic/generators/tests/test_noise_generator.py new file mode 100644 index 000000000..e49086126 --- /dev/null +++ b/signal_processing/synthetic/generators/tests/test_noise_generator.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import numpy as np + +from ..noise_generator import NoiseChannelConfig, NoiseGenerator + + +def test_generate_noise() -> None: + """ + Tests that the samples generated from each channel matches the parameters + specified in their configuration. Since this is noise, only check the bounds + """ + + num_samples = 10 + + # Test configurations + shape = (2,) + amplitudes = np.array([2.0, 5.0]) + offsets = np.array([1.0, -2.5]) + sample_rate = 1 + + config = NoiseChannelConfig(shape, amplitudes, offsets, sample_rate) + + # The generator + generator = NoiseGenerator(config) + + max_values = np.array([amplitudes * np.ones(shape) + offsets] * num_samples) + min_values = np.array([offsets] * num_samples) + # Generate expected values + values = np.array([generator.next_sample().data for i in range(num_samples)]) + assert values[0].shape == shape + np.testing.assert_array_less(values, max_values) + # Note: small delta subtraction is because the generator produces samples on + # the half open intervals [offsets, amplitudes+offsets) + np.testing.assert_array_less(min_values - 0.00001, values) diff --git a/signal_processing/synthetic/generators/tests/test_sine_wave_generator.py b/signal_processing/synthetic/generators/tests/test_sine_wave_generator.py new file mode 100644 index 000000000..a7666dbb1 --- /dev/null +++ b/signal_processing/synthetic/generators/tests/test_sine_wave_generator.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import numpy as np + +from ..sine_wave_generator import SineWaveChannelConfig, SineWaveGenerator + + +def test_generate_sinusoid() -> None: + """ + Tests that the samples generated from each channel matches the parameters + specified in their configuration. + """ + + test_clock_frequency = 100 # hz + test_duration = 300 # sec + + # test configurations + shape = (2,) + amplitudes = np.array([5.0, 3.0]) + frequencies = np.array([5, 10]) + phase_shifts = np.array([1.0, 5.0]) + midlines = np.array([3.0, -2.5]) + sample_rate = test_clock_frequency + + config = SineWaveChannelConfig( + shape, amplitudes, frequencies, phase_shifts, midlines, sample_rate + ) + + # The generator + generator = SineWaveGenerator(config) + + # Generate expected values + t_s = np.arange(0, test_duration, 1 / test_clock_frequency) + angles = np.expand_dims(frequencies, 1) * np.expand_dims(2 * np.pi * t_s, 0) + angles = angles + np.expand_dims(phase_shifts, 1) + expected = np.expand_dims(amplitudes, 1) * np.sin(angles) + np.expand_dims( + midlines, 1 + ) + + values = [generator.next_sample().data for t in t_s] + values = np.array(values).T + np.testing.assert_almost_equal(values, expected) + + +if __name__ == "__main__": + test_generate_sinusoid() diff --git a/signal_processing/synthetic/nodes/__init__.py b/signal_processing/synthetic/nodes/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/synthetic/nodes/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/synthetic/nodes/mixer_one_input_node.py b/signal_processing/synthetic/nodes/mixer_one_input_node.py new file mode 100644 index 000000000..2df93ecbe --- /dev/null +++ b/signal_processing/synthetic/nodes/mixer_one_input_node.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +""" Simple 1 input mixer node. This takes the input of M channels, and produces + an output of N channels, using simple matrix mulitplication. +""" + +import labgraph as lg +import numpy as np +from ...messages.generic_signal_sample import SignalSampleMessage + + +class MixerOneInputConfig(lg.Config): + # This is an NxM matrix (for M inputs, N outputs) + weights: np.ndarray + + +class MixerOneInputNode(lg.Node): + + IN_SAMPLE_TOPIC = lg.Topic(SignalSampleMessage) + OUT_SAMPLE_TOPIC = lg.Topic(SignalSampleMessage) + + @lg.subscriber(IN_SAMPLE_TOPIC) + @lg.publisher(OUT_SAMPLE_TOPIC) + async def mix_samples(self, in_sample: SignalSampleMessage) -> lg.AsyncPublisher: + if in_sample.sample.shape[0] != self.config.weights.shape[1]: + raise lg.util.LabgraphError("Mismatching input dimensions") + out_sample = SignalSampleMessage( + timestamp=in_sample.timestamp, + sample=np.dot(self.config.weights, in_sample.sample), + ) + yield self.OUT_SAMPLE_TOPIC, out_sample diff --git a/signal_processing/synthetic/nodes/mixer_two_input_node.py b/signal_processing/synthetic/nodes/mixer_two_input_node.py new file mode 100644 index 000000000..aa10b40ff --- /dev/null +++ b/signal_processing/synthetic/nodes/mixer_two_input_node.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +""" Simple 2 input mixer node. This takes two inputs of potentially different sizes, + weights them and then sums them to form an output. +""" +import asyncio +import queue + +import labgraph as lg +import numpy as np +from ...messages.generic_signal_sample import SignalSampleMessage + + +SHORT_SLEEP_SECS = 0.001 + + +class MixerTwoInputConfig(lg.Config): + # These are NxL and NxR matrices (for L,R inputs, same dimension N outputs) + left_weights: np.ndarray + right_weights: np.ndarray + + +class MixerTwoInputNode(lg.Node): + + IN_LEFT_SAMPLE_TOPIC = lg.Topic(SignalSampleMessage) + IN_RIGHT_SAMPLE_TOPIC = lg.Topic(SignalSampleMessage) + OUT_SAMPLE_TOPIC = lg.Topic(SignalSampleMessage) + + def setup(self) -> None: + # Check the weights are correctly dimensioned to be summed on output + if self.config.left_weights.shape[0] != self.config.right_weights.shape[0]: + raise ValueError("Mismatch in left-right output dimensions") + self._left_in = queue.Queue() + self._right_in = queue.Queue() + self._shutdown = asyncio.Event() + + def cleanup(self) -> None: + self._shutdown.set() + + @lg.subscriber(IN_LEFT_SAMPLE_TOPIC) + def left_input(self, in_sample: SignalSampleMessage) -> None: + # Check in the input dimensions against left weights + if in_sample.sample.shape[0] != self.config.left_weights.shape[1]: + raise ValueError("Mismatch in left input dimension") + self._left_in.put(in_sample) + + @lg.subscriber(IN_RIGHT_SAMPLE_TOPIC) + def right_input(self, in_sample: SignalSampleMessage) -> None: + # Check in the input dimensions against right weights + if in_sample.sample.shape[0] != self.config.right_weights.shape[1]: + raise ValueError("Mismatch in right input dimension") + self._right_in.put(in_sample) + + @lg.publisher(OUT_SAMPLE_TOPIC) + async def mix_samples(self) -> lg.AsyncPublisher: + while not self._shutdown.is_set(): + while self._left_in.empty() or self._right_in.empty(): + await asyncio.sleep(SHORT_SLEEP_SECS) + left = self._left_in.get() + right = self._right_in.get() + mixed_output = np.dot(self.config.left_weights, left.sample) + np.dot( + self.config.right_weights, right.sample + ) + # I am just using the left timetamp for this, since I don't + # know what else makes sense! + out_sample = SignalSampleMessage( + timestamp=left.timestamp, sample=mixed_output + ) + yield self.OUT_SAMPLE_TOPIC, out_sample diff --git a/signal_processing/synthetic/nodes/signal_capture_node.py b/signal_processing/synthetic/nodes/signal_capture_node.py new file mode 100644 index 000000000..8bf14db4f --- /dev/null +++ b/signal_processing/synthetic/nodes/signal_capture_node.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +""" This is a simple node to capture SignalSampleMessage's useful for testing. + NOTE: This needs to run in the same process as the test for this to work, + since you need to be able to directly access memory from the capture node +""" + +import asyncio +from typing import List + +import labgraph as lg +from ...messages.generic_signal_sample import SignalSampleMessage + + +class SignalCaptureConfig(lg.Config): + num_capture: int + + +class SignalCaptureNode(lg.Node): + """Node that captures a number of messages, and then terminates""" + + SAMPLE_TOPIC = lg.Topic(SignalSampleMessage) + + def setup(self): + self._shutdown = asyncio.Event() + self._samples: List[SignalSampleMessage] = [] + + @lg.subscriber(SAMPLE_TOPIC) + def sample_sink(self, message: SignalSampleMessage) -> None: + if not self._shutdown.is_set(): + self._samples.append(message.sample) + if len(self._samples) >= self.config.num_capture: + self._shutdown.set() + raise lg.NormalTermination() + + @property + def samples(self): + return self._samples diff --git a/signal_processing/synthetic/nodes/signal_generator_node.py b/signal_processing/synthetic/nodes/signal_generator_node.py new file mode 100644 index 000000000..5a8d97443 --- /dev/null +++ b/signal_processing/synthetic/nodes/signal_generator_node.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +""" Node for a generic signal generator, eg SineWaveGenerator """ + +import asyncio + +import labgraph as lg +from ...messages.generic_signal_sample import SignalSampleMessage +from labgraph.simulations import FunctionGeneratorNode + + +# This assume we would not be delivering samples at close to 1KHz +PUBLISHER_SLEEP_SECS = 0.001 + + +class SignalGeneratorNode(FunctionGeneratorNode): + + SAMPLE_TOPIC = lg.Topic(SignalSampleMessage) + + def setup(self): + self._shutdown = asyncio.Event() + + def cleanup(self): + self._shutdown.set() + + @lg.publisher(SAMPLE_TOPIC) + async def publish_samples(self) -> lg.AsyncPublisher: + while not self._shutdown.is_set(): + sample_message = self._generator.next_sample() + yield self.SAMPLE_TOPIC, SignalSampleMessage( + timestamp=sample_message.timestamp, sample=sample_message.data + ) + await asyncio.sleep(PUBLISHER_SLEEP_SECS) diff --git a/signal_processing/synthetic/nodes/tests/__init__.py b/signal_processing/synthetic/nodes/tests/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/synthetic/nodes/tests/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/synthetic/nodes/tests/test_mixer_one_input_node.py b/signal_processing/synthetic/nodes/tests/test_mixer_one_input_node.py new file mode 100644 index 000000000..dcefa6f49 --- /dev/null +++ b/signal_processing/synthetic/nodes/tests/test_mixer_one_input_node.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import labgraph as lg +import numpy as np +import pytest +from ...generators.sine_wave_generator import ( + SineWaveChannelConfig, + SineWaveGenerator, +) + +from ..mixer_one_input_node import MixerOneInputConfig, MixerOneInputNode +from ..signal_capture_node import SignalCaptureConfig, SignalCaptureNode +from ..signal_generator_node import SignalGeneratorNode + + +class MyGraphConfig(lg.Config): + sine_wave_channel_config: SineWaveChannelConfig + mixer_config: MixerOneInputConfig + capture_config: SignalCaptureConfig + + +class MyGraph(lg.Graph): + + sample_source: SignalGeneratorNode + mixer_node: MixerOneInputNode + capture_node: SignalCaptureNode + + def setup(self) -> None: + self.capture_node.configure(self.config.capture_config) + self.sample_source.set_generator( + SineWaveGenerator(self.config.sine_wave_channel_config) + ) + self.mixer_node.configure(self.config.mixer_config) + + def connections(self) -> lg.Connections: + return ( + (self.mixer_node.IN_SAMPLE_TOPIC, self.sample_source.SAMPLE_TOPIC), + (self.capture_node.SAMPLE_TOPIC, self.mixer_node.OUT_SAMPLE_TOPIC), + ) + + +def test_mixer_one_input_node() -> None: + """ + Tests that node mixes correctly, uses sine wave as input + """ + + sample_rate = 1 # Hz + test_duration = 10 # sec + + # Test configurations + shape = (2,) + amplitudes = np.array([5.0, 3.0]) + frequencies = np.array([5, 10]) + phase_shifts = np.array([1.0, 5.0]) + midlines = np.array([3.0, -2.5]) + + # Generate expected values + t_s = np.arange(0, test_duration, 1 / sample_rate) + angles = np.expand_dims(frequencies, 1) * np.expand_dims(2 * np.pi * t_s, 0) + angles = angles + np.expand_dims(phase_shifts, 1) + expected = np.expand_dims(amplitudes, 1) * np.sin(angles) + np.expand_dims( + midlines, 1 + ) + + # Create the graph + generator_config = SineWaveChannelConfig( + shape, amplitudes, frequencies, phase_shifts, midlines, sample_rate + ) + capture_config = SignalCaptureConfig(int(test_duration / sample_rate)) + mixer_weights = np.identity(2) + mixer_config = MixerOneInputConfig(mixer_weights) + my_graph_config = MyGraphConfig(generator_config, mixer_config, capture_config) + graph = MyGraph() + graph.configure(my_graph_config) + + runner = lg.LocalRunner(module=graph) + runner.run() + received = np.array(graph.capture_node.samples).T + np.testing.assert_almost_equal(received, expected) diff --git a/signal_processing/synthetic/nodes/tests/test_mixer_two_input_node.py b/signal_processing/synthetic/nodes/tests/test_mixer_two_input_node.py new file mode 100644 index 000000000..66ba34778 --- /dev/null +++ b/signal_processing/synthetic/nodes/tests/test_mixer_two_input_node.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +""" Simple test for a two input mixer """ + +import labgraph as lg +import numpy as np +import pytest +from ...generators.sine_wave_generator import ( + SineWaveChannelConfig, + SineWaveGenerator, +) + +from ..mixer_two_input_node import MixerTwoInputConfig, MixerTwoInputNode +from ..signal_capture_node import SignalCaptureConfig, SignalCaptureNode +from ..signal_generator_node import SignalGeneratorNode + + +class MyGraphConfig(lg.Config): + sine_wave_channel_config: SineWaveChannelConfig + mixer_config: MixerTwoInputConfig + capture_config: SignalCaptureConfig + + +class MyGraph(lg.Graph): + + sample_source: SignalGeneratorNode + mixer_node: MixerTwoInputNode + capture_node: SignalCaptureNode + + def setup(self) -> None: + self.capture_node.configure(self.config.capture_config) + self.sample_source.set_generator( + SineWaveGenerator(self.config.sine_wave_channel_config) + ) + self.mixer_node.configure(self.config.mixer_config) + + def connections(self) -> lg.Connections: + return ( + # Note connecting both sides of the mixer to the same input for this test + (self.mixer_node.IN_LEFT_SAMPLE_TOPIC, self.sample_source.SAMPLE_TOPIC), + (self.mixer_node.IN_RIGHT_SAMPLE_TOPIC, self.sample_source.SAMPLE_TOPIC), + (self.capture_node.SAMPLE_TOPIC, self.mixer_node.OUT_SAMPLE_TOPIC), + ) + + +def test_mixertwo_input_node() -> None: + """ + Tests that node mixes correctly, uses sine wave as input + """ + + sample_rate = 1 # Hz + test_duration = 10 # sec + + # Test configurations + shape = (2,) + amplitudes = np.array([5.0, 3.0]) + frequencies = np.array([5, 10]) + phase_shifts = np.array([1.0, 5.0]) + midlines = np.array([3.0, -2.5]) + + # Generate expected values + t_s = np.arange(0, test_duration, 1 / sample_rate) + angles = np.expand_dims(frequencies, 1) * np.expand_dims(2 * np.pi * t_s, 0) + angles = angles + np.expand_dims(phase_shifts, 1) + expected = np.expand_dims(amplitudes, 1) * np.sin(angles) + np.expand_dims( + midlines, 1 + ) + + # Create the graph + generator_config = SineWaveChannelConfig( + shape, amplitudes, frequencies, phase_shifts, midlines, sample_rate + ) + capture_config = SignalCaptureConfig(int(test_duration / sample_rate)) + mixer_left_weights = 0.5 * np.identity(2) + mixer_right_weights = 0.5 * np.identity(2) + mixer_config = MixerTwoInputConfig(mixer_left_weights, mixer_right_weights) + my_graph_config = MyGraphConfig(generator_config, mixer_config, capture_config) + graph = MyGraph() + graph.configure(my_graph_config) + + runner = lg.LocalRunner(module=graph) + runner.run() + received = np.array(graph.capture_node.samples).T + np.testing.assert_almost_equal(received, expected) diff --git a/signal_processing/synthetic/nodes/tests/test_sine_wave_generator_node.py b/signal_processing/synthetic/nodes/tests/test_sine_wave_generator_node.py new file mode 100644 index 000000000..50e2b6c52 --- /dev/null +++ b/signal_processing/synthetic/nodes/tests/test_sine_wave_generator_node.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import labgraph as lg +import numpy as np +from ...generators.sine_wave_generator import ( + SineWaveChannelConfig, + SineWaveGenerator, +) + +from ..signal_capture_node import SignalCaptureConfig, SignalCaptureNode +from ..signal_generator_node import SignalGeneratorNode + + +SHORT_SLEEP_SECS = 0.01 + + +class MyGraphConfig(lg.Config): + capture_config: SignalCaptureConfig + sine_wave_channel_config: SineWaveChannelConfig + + +class MyGraph(lg.Graph): + # Note any nodes must be defined here, since connections are checked + # before on creation of the class, i.e., before setup is called + capture_node: SignalCaptureNode + sample_source: SignalGeneratorNode + + def setup(self): + self.capture_node.configure(self.config.capture_config) + self.sample_source.set_generator( + SineWaveGenerator(self.config.sine_wave_channel_config) + ) + + def connections(self) -> lg.Connections: + return ((self.capture_node.SAMPLE_TOPIC, self.sample_source.SAMPLE_TOPIC),) + + +def test_sine_wave_generator_node() -> None: + """ + Tests that node configures itself correctly and returns samples from the generator. + """ + + sample_rate = 1 # Hz + test_duration = 10 # sec + + # Test configurations + shape = (2,) + amplitudes = np.array([5.0, 3.0]) + frequencies = np.array([5, 10]) + phase_shifts = np.array([1.0, 5.0]) + midlines = np.array([3.0, -2.5]) + + # Generate expected values + t_s = np.arange(0, test_duration, 1 / sample_rate) + angles = np.expand_dims(frequencies, 1) * np.expand_dims(2 * np.pi * t_s, 0) + angles = angles + np.expand_dims(phase_shifts, 1) + expected = np.expand_dims(amplitudes, 1) * np.sin(angles) + np.expand_dims( + midlines, 1 + ) + + # Create the graph + generator_config = SineWaveChannelConfig( + shape, amplitudes, frequencies, phase_shifts, midlines, sample_rate + ) + capture_config = SignalCaptureConfig(int(test_duration / sample_rate)) + my_graph_config = MyGraphConfig(capture_config, generator_config) + + graph = MyGraph() + graph.configure(my_graph_config) + + runner = lg.LocalRunner(module=graph) + runner.run() + received = np.array(graph.capture_node.samples).T + np.testing.assert_almost_equal(received, expected) diff --git a/signal_processing/synthetic_data/README.md b/signal_processing/synthetic_data/README.md new file mode 100644 index 000000000..41af08ab8 --- /dev/null +++ b/signal_processing/synthetic_data/README.md @@ -0,0 +1,13 @@ +# Synthetic data + +## What this folder contains +Files in this directory relate to generation and testing of synthetic data + +## Folder structure +- dummy_generator.py: functions to generate dummy physiological signals +- README: this file +- tests: folder that contains all the unit tests + - test_dummy_generator.py + +## Example use +- Check tests/test_dummy_generator.py diff --git a/signal_processing/synthetic_data/__init__.py b/signal_processing/synthetic_data/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/synthetic_data/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/synthetic_data/additive_synth_dataset.py b/signal_processing/synthetic_data/additive_synth_dataset.py new file mode 100644 index 000000000..9d69692e7 --- /dev/null +++ b/signal_processing/synthetic_data/additive_synth_dataset.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from typing import List, Optional, Tuple, Union + +import numpy as np +from colorednoise import powerlaw_psd_gaussian +from .dummy_generator import ( + dummy_physiological_noise, + motion_noise, + simulated_hemodynamics, +) +from torch.utils.data import Dataset + + +class AdditiveSynthDataset(Dataset): + """ + Generates a synthetic finger-tapping fNIRS dataset using the same + additive noise model as: + Scarpa, F., Brigadoi, S., Cutini, S., Scatturin, P., Zorzi, M., + Dell’Acqua, R., & Sparacino, G. (2013). A reference-channel based + methodology to improve estimation of event-related hemodynamic + response from fNIRS measurements. + NeuroImage, 72, 106–119. https://doi.org/10.1016/j.neuroimage.2013.01.021 + + Default parameters are as best as we could match that paper, but the paper + was ambiguous in many ways, so no guarantees. + """ + + def __init__( + self, + n_trials: int, + trial_length: float, + channel_dists: np.ndarray, + class_proportions: Optional[List[float]] = None, + sample_rate: int = 5, + ): + self.n_trials = n_trials + self.trial_length = trial_length + self.channel_dists = channel_dists + self.n_channels = len(channel_dists) + self.sample_rate = sample_rate + self.trial_samples = int(trial_length * sample_rate) + + if class_proportions is None: + class_proportions = [1 / 3] * 3 + + self.labels = np.random.choice( + np.arange(len(class_proportions)), + size=n_trials, + replace=True, + p=class_proportions, + ) + + self.X = np.zeros((n_trials, self.n_channels, self.trial_samples)) + + hrf_scales = self.hrf_scale_from_channel_dists(channel_dists) + + for trial in range(n_trials): + for channel, hrf_scale in enumerate(hrf_scales): + # HACK: fragile: assume label 0 means no HRF, + # label 1 means left and left is first half of channels, + # label 2 means right and right is 2nd half of channels, + # otherwise 0 + if ( + self.labels[trial] == 0 + or (self.labels[trial] == 1 and channel > (self.n_channels // 2)) + or (self.labels[trial] == 2 and channel <= (self.n_channels // 2)) + ): + hrf_scale = 0 + + self.X[trial, channel, :] = self.generate_synth_data(hrf_scale) + + def hrf_scale_from_channel_dists(self, channel_dists: np.ndarray) -> np.ndarray: + """ + hrf weight (k in the paper) is a function of channel dists, + if we follow the original paper + there's 2 cutoffs and k is 0 for surface/close dists, 0.5 for "medium" + dists, and 1.0 for "long"/deep dists + they don'ts ay what "medium" dists are but elsewhere in the paper + they say distances above 1.5cm but below 3cm are too + short to be standard channel but aren't reference channels, so we assume + those are "medium". + """ + + hrf_scale = np.zeros_like(channel_dists) + hrf_scale[channel_dists > 15] = 0.5 + # distances 3cm or above are standard channels + hrf_scale[channel_dists >= 30] = 1.0 + return hrf_scale + + def __len__(self) -> int: + return self.n_trials + + def __getitem__(self, index: Union[int, slice]) -> Tuple[np.ndarray, np.ndarray]: + return self.X[index, ...], self.labels[index] + + def sim_physio_noise( + self, + frequencies_mean: Optional[List[float]] = None, + frequencies_sd: Optional[List[float]] = None, + amplitudes_mean: Optional[List[float]] = None, + amplitudes_sd: Optional[List[float]] = None, + ) -> np.ndarray: + """ + Physiological noise + Per paper table 1, there are 5 components: + very low freq, low freq, vasomotor, respiratory, cardiac (given in + defaults) + """ + if frequencies_mean is None: + frequencies_mean = [0.002, 0.01, 0.07, 0.2, 1.1] + if frequencies_sd is None: + frequencies_sd = [0.0001, 0.001, 0.04, 0.03, 0.1] + if amplitudes_mean is None: + amplitudes_mean = [700, 700, 400, 200, 400] + if amplitudes_sd is None: + amplitudes_sd = [100, 100, 10, 10, 10] + + physio_noises = [ + dummy_physiological_noise( + amplitude=np.random.normal(a_mean, a_sd), + sample_rate=self.sample_rate, + interest_freq=np.random.normal(f_mean, f_sd), + phase=np.random.normal() * 2 * np.pi, + duration=self.trial_length, + ) + for f_mean, f_sd, a_mean, a_sd in zip( + frequencies_mean, frequencies_sd, amplitudes_mean, amplitudes_sd + ) + ] + + return np.sum(np.stack(physio_noises), axis=0) + + def generate_measurement_noise( + self, + exponent: float = 0, + amplitude: float = 400, + sd: float = 180, + fmin: float = 1, + ) -> np.ndarray: + """ + white noise in original paper, amplitude in the paper is 400 ± 180, + TODO: mean and sd doesn't really make sense for non-white noise + """ + eta = powerlaw_psd_gaussian( + exponent=exponent, + size=self.trial_length * self.sample_rate, + fmin=fmin, + ) + + return eta / np.mean(eta) * sd + amplitude + + def generate_hrf( + self, hrf_scale: float = 1, amplitude: float = 420.0 + ) -> np.ndarray: + """ + From the paper, + "In order to simulate the HR due to two different + stimuli and with shapes, amplitudes and latencies in + agreement with previous findings regarding finger + tapping tasks, two utrue profiles were generated + by properly tuning the parameters in Eq. (2), + allowing small variations in peak amplitude and + latency between a trial and another. For HbO, this led to a + first HR profile with a peak amplitude of 420±20 nM and a peak + latency equal to 5.0±.2 s, while the second HR profile had a + peak amplitude of 360±20 nM and a peak latency equal to 5.5±.2 s." + So basically we have no idea what the parameters are. + + hrf_scale here is `k` in the paper. Eventually we may want very close + source/detector pairs to have hrf_scale=0, saturating to hrf_scale=1. + """ + + u_true = hrf_scale * simulated_hemodynamics( + amplitude=amplitude, + sample_rate=self.sample_rate, + duration=self.trial_length, + ) + return u_true + + def generate_motion_noise(self) -> np.ndarray: + """ + No idea what motion params are? + """ + return motion_noise( + motion_amplitude=500, + motion_duration_mean=0.5, + sample_rate=self.sample_rate, + sample_duration=self.trial_length, + ) + + def generate_synth_data(self, hrf_scale: float = 1) -> np.ndarray: + """ + y(t) = hrf_scale * u_true(t) + \\phi_sim(t) + \\eta(t) + r(t), where: + - hrf_scale is a scaling coefficient based on depth + (in the original paper this is 0 for ref channels, + 1 for signal channels, and 0.5 for some other "standard channels") + - u_true(t) is the true HRF, made by simulated_hemodynamics() + - \\phi_sim(t) is the physiological noise, made from sim_physio_noise() + - \\eta(t) is "random" or white noise + - r(t) is motion noise + """ + + phi_sim = self.sim_physio_noise() + u_true = self.generate_hrf(hrf_scale) + measurement_noise = self.generate_measurement_noise() + motion_noise = self.generate_motion_noise() + return u_true + phi_sim + measurement_noise + motion_noise diff --git a/signal_processing/synthetic_data/additive_synth_dataset_variant.py b/signal_processing/synthetic_data/additive_synth_dataset_variant.py new file mode 100644 index 000000000..d5c82bf87 --- /dev/null +++ b/signal_processing/synthetic_data/additive_synth_dataset_variant.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import random +from typing import List, Optional, Tuple, Union + +import numpy as np +from colorednoise import powerlaw_psd_gaussian +from ..utils.doc.docfill import fill_in_docstring +from .dummy_generator import ( + dummy_physiological_noise, + simulated_hemodynamics, +) +from scipy import signal + +############################################################################### +# Documentation for Synthesized Data # +############################################################################### +_common_docs = fill_in_docstring( + { + "n_trials": """channel_data : int + Number of trials in the experiment including all conditions. + """, + "trial_length": """trial_length : float + Length of each trial in seconds + """, + "class_labels": """class_labels: List[str] + class_labels is a list of strings defining the labels or conditions + """, + "channel_dists": """channel_dists : np.ndarray + SD distance array for all channels + """, + "hrf_scale_for_cond": """ hrf_scale_for_cond : np.ndarray [n_channels, n_conds] + hrf_scale_for_condition to scale the data based on condition. + n_channels is the number of channels + n_conds is the the number of conditions or class_labels + details. + """, + "sample_rate": """ sample_rate : float + Sampling frequency of acquisition in Hz + """, + } +) + + +@_common_docs +class AdditiveSynthDatasetVariant(object): + """ + based on the combining the ideas of 2 papers + 1. Scholkmann F, Spichtig S, Muehlemann T, Wolf M. + How to detect and reduce movement artifacts in near-infrared imaging + using moving standard deviation and spline interpolation. + Physiol Meas. 2010 May;31(5):649-62. + doi: 10.1088/0967-3334/31/5/004. Epub 2010 Mar 22. PMID: 20308772. (Default is this one) + + 2. Scarpa, F., Brigadoi, S., Cutini, S., Scatturin, P., Zorzi, M., + Dell’Acqua, R., & Sparacino, G. (2013). A reference-channel based + methodology to improve estimation of event-related hemodynamic + response from fNIRS measurements. + NeuroImage, 72, 106–119. https://doi.org/10.1016/j.neuroimage.2013.01.021 + + """ + + def __init__( + self, + n_trials: int, + trial_length: float, + channel_dists: np.ndarray, + class_labels: List[str], + hrf_scale_for_cond: np.ndarray, + sample_rate: float = 10.0, + ): + self.n_trials = n_trials + self.trial_length = trial_length + self.channel_dists = channel_dists + self.n_channels = len(channel_dists) + self.sample_rate = sample_rate + self.trial_samples = int(trial_length * sample_rate) + self.labels = class_labels + + self.X = np.zeros((n_trials, self.n_channels, self.trial_samples)) + + hrf_scales_from_distance = hrf_scale_from_channel_dists(channel_dists) + + for trial in range(n_trials): + for label in range(len(class_labels)): + for channel, hrf_scale_from_distance in enumerate( + hrf_scales_from_distance + ): + hrf_scale_final = ( + hrf_scale_from_distance * hrf_scale_for_cond[channel, label] + ) + + self.X[trial, channel, :] = generate_synth_data( + hrf_scale_final, self.sample_rate, self.trial_length + ) + + def __len__(self) -> int: + return self.n_trials + + def __getitem__(self, index: Union[int, slice]) -> Tuple[np.ndarray, np.ndarray]: + return self.X[index, ...], self.labels[index] + + +def hrf_scale_from_channel_dists(channel_dists: np.ndarray) -> np.ndarray: + """ + Assuming hrf_scales would attenuate linearly with SD distance + """ + + hrf_scale = np.zeros_like(channel_dists) + hrf_scale[np.array(channel_dists) < 10] = 0.5 + hrf_scale[np.array(channel_dists) >= 10] = 1.00 + hrf_scale[np.array(channel_dists) >= 20] = 0.50 + hrf_scale[np.array(channel_dists) >= 30] = 0.33 + hrf_scale[np.array(channel_dists) >= 40] = 0.25 + hrf_scale[np.array(channel_dists) >= 50] = 0.20 + hrf_scale[np.array(channel_dists) >= 60] = 0.16 + return hrf_scale + + +def sim_physio_noise( + sample_rate: float, + trial_length: float, + frequencies_mean: Optional[List[float]] = None, + frequencies_sd: Optional[List[float]] = None, + amplitudes_mean: Optional[List[float]] = None, + amplitudes_sd: Optional[List[float]] = None, +) -> np.ndarray: + """ + Sample_rate in Hz and trial length in seconds + from the paper Scholkmann et al, 2010 + The amplitude and frequency values of each sine wave were defined according to the mean frequencies of real NIRI signals + + very high frequency oscillation (heart rate, f = 1 Hz, µ = 0.6) + high frequency oscillation (respiration, f = 0.25 Hz, µ = 0.2) + low frequency oscillation (f = 0.1 Hz, µ = 0.9) + very low frequency oscillation (f = 0.04 Hz, µ = 1). + + """ + if frequencies_mean is None: + frequencies_mean = [0.002, 0.03, 0.1, 0.25, 1.1] + if frequencies_sd is None: + frequencies_sd = [0.0001, 0.01, 0.03, 0.05, 0.2] + if amplitudes_mean is None: + amplitudes_mean = [0.7, 0.9, 0.9, 0.2, 0.6] + if amplitudes_sd is None: + amplitudes_sd = [0.01, 0.01, 0.1, 0.1, 0.1] + + physio_noises = [ + dummy_physiological_noise( + amplitude=np.random.normal(a_mean, a_sd), + sample_rate=sample_rate, + interest_freq=np.random.normal(f_mean, f_sd), + phase=np.random.normal() * 2 * np.pi, + duration=trial_length, + ) + for f_mean, f_sd, a_mean, a_sd in zip( + frequencies_mean, frequencies_sd, amplitudes_mean, amplitudes_sd + ) + ] + + return np.sum(np.stack(physio_noises), axis=0) + + +def generate_measurement_noise(sample_rate: float, trial_length: float) -> np.ndarray: + + exponent = 1 + amplitude = 0.4 + sd = 0.018 + fmin = 0 + + eta = powerlaw_psd_gaussian( + exponent=exponent, size=int(trial_length * sample_rate), fmin=fmin + ) + + return eta / np.mean(eta) * sd + amplitude + + +def generate_hrf( + hrf_scale: float, sample_rate: float, trial_length: float +) -> np.ndarray: + """ + From the paper, + "In order to simulate the HR due to two different + stimuli and with shapes, amplitudes and latencies in + agreement with previous findings regarding finger + tapping tasks, two utrue profiles were generated + by properly tuning the parameters in Eq. (2), + allowing small variations in peak amplitude and + latency between a trial and another. For HbO, this led to a + first HR profile with a peak amplitude of 420±20 nM and a peak + latency equal to 5.0±.2 s, while the second HR profile had a + peak amplitude of 360±20 nM and a peak latency equal to 5.5±.2 s." + So basically we have no idea what the parameters are. + + hrf_scale here is `k` in the paper. Eventually we may want very close + source/detector pairs to have hrf_scale=0, saturating to hrf_scale=1 + on pairs. + """ + amplitude = 1 + + u_true = hrf_scale * simulated_hemodynamics( + amplitude=amplitude, sample_rate=sample_rate, duration=trial_length + ) + return u_true + + +def generate_motion_noise(sample_rate: float, trial_length: float) -> np.ndarray: + """ + The better simulation of motion artifacts in Scholkmann et al, 2010 than Scarpa et al, 2013 + motivated to reproduce a variant of this paper + Here Motion = Base Shift + pulse + temporary LF oscillation + Spike (spike added; not in original paper) + + """ + # Pulse + """ + Simulating a pulse with values between [-4 4] and in the entire duration + """ + duration = int(trial_length * sample_rate) + pulse = np.zeros(duration) + + pulse[random.randint(1, int(len(pulse) / 3))] = random.randint(-4, 4) + + # Shift + """ + Simulating a base shift with values between [-4 4] and in the 1/4th of entire duration + """ + shift = np.zeros(duration) + shift[ + random.randint(1, len(shift) // 4) : random.randint( + len(shift) // 4, len(shift) // 2 + ) + ] = random.randint(-4, 4) + + # temporary LF oscillation + """ + Not exactly clear what motion it corresponds to probably due to slouching or slow head movement + Using a 10s Gaussian wave to define it + """ + LFO = np.zeros(duration) + start = len(LFO) // 4 + end = 3 * len(LFO) // 4 + LFO[start:end] = signal.gaussian(len(LFO) // 2, std=len(LFO) // 20) + + # spikes + """ + Using a spikes 0f 2s duration, similar to head motion or body motion + """ + spike = np.zeros(duration) + + loc, scale = random.randint(0, 1), random.randint(0, 1) + s = np.random.laplace(loc, scale, 20) + start = random.randint(1, len(spike) - 20) + end = start + 20 + spike[start:end] = s + + """ + It returns motion for each trial. It is highly improbable that all types of motion are present within one trial. + So returning a random combination of motions (including no motion for a trial) + e.g. one trial can have no motion or no_motion+spikes + """ + no_motion = np.zeros(duration) + motions = [no_motion, pulse, shift, LFO, spike] + motion = sum(motions[: random.randint(0, 5)]) + if isinstance(motion, list) and len(motion) == 0: + return no_motion + elif np.array(motion).size == 1: + return no_motion + else: + return np.array(motion) + + +def generate_synth_data( + hrf_scale: float, sample_rate: float, trial_length: float +) -> np.ndarray: + """ + y(t) = hrf_scale * u_true(t) + \\phi_sim(t) + \\eta(t) + r(t), where: + - u_true(t) is the true HRF, made by simulated_hemodynamics() + - \\phi_sim(t) is the physiological noise, made from sim_physio_noise() + - \\eta(t) is "random" or white noise + - r(t) is motion noise + """ + sample_rate = sample_rate + trial_length = trial_length + hrf_scale = hrf_scale + + phi_sim = sim_physio_noise(sample_rate, trial_length) + + u_true = generate_hrf(hrf_scale, sample_rate, trial_length) + + measurement_noise = generate_measurement_noise(sample_rate, trial_length) + + motion_noise = generate_motion_noise(sample_rate, trial_length) + + return u_true + phi_sim + measurement_noise + motion_noise diff --git a/signal_processing/synthetic_data/dummy_generator.py b/signal_processing/synthetic_data/dummy_generator.py new file mode 100644 index 000000000..478796f19 --- /dev/null +++ b/signal_processing/synthetic_data/dummy_generator.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import logging + +import numpy as np +import scipy.stats + +# colored noise related +from numpy import sqrt, newaxis +from numpy import sum as npsum +from numpy.fft import irfft, rfftfreq +from numpy.random import normal +from scipy.stats import truncnorm + + +def _make_time_index(duration: float, sample_rate: float) -> np.ndarray: + """makes indices for a time series of given duration (in s) + and sample rate (in hz) + """ + if duration < 1 / sample_rate: + raise RuntimeError( + f"sample rate {sample_rate}\ + too slow for duration {duration}!" + ) + return np.arange(0, duration, 1 / sample_rate) + + +def gamma_shape_rate(x: np.ndarray, shape: float, rate: float) -> np.ndarray: + """ + scipy.stats.gamma is parameterized by shape and scale=1/rate, + we flip this for convenience + """ + scale = 1 / rate + return scipy.stats.gamma.pdf(x, a=shape, scale=scale) + + +def double_gamma_hrf( + t: float, + A: float, + alpha1: float = 6.0, + alpha2: float = 16.0, + beta1: float = 1, + beta2: float = 1, + c: float = 1 / 6, +) -> np.ndarray: + """ + HRF as linear combination of two gamma distributions + Default parameters match: + https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3318970/#FD7 + """ + return A * ( + gamma_shape_rate(t, alpha1, beta1) - c * gamma_shape_rate(t, alpha2, beta2) + ) + + +def simulated_hemodynamics( + amplitude: float, duration: float, sample_rate: float, pad_len: int = 0 +) -> np.ndarray: + """Generate simulated hemodynamics + + Generate simulated hemodynamics using double-gamma HRF over + a particular window and sample rate + + Attributes: + ---------- + pad_len: int + how many samples to shift the HRF + [Default = 0] + e.g. pad_len = 50 + shift the HRF start by 50 samples + """ + ts = _make_time_index(duration, sample_rate) + # canonical HRF is in seconds + hrf = double_gamma_hrf(ts, amplitude) + # shift the initiation point + # only left padding to shift with constant 0 + if int(duration * sample_rate) == 0: + logging.warning( + "Your int(duration * sample_rate) is zero. \ + Returned array will be empty." + ) + if pad_len > int(duration * sample_rate): + logging.warning( + "Your pad_len is larger than the whole data. \ + Returned array will be all zeros." + ) + hrf = np.pad(hrf, (pad_len, 0), "constant")[: int(duration * sample_rate)] + + return hrf + + +def dummy_physiological_noise( + amplitude: float, + sample_rate: float, + interest_freq: float, + phase: float, + duration: float, +) -> np.ndarray: + """Generate dummy physiological noise + + Attributes: + ---------- + amplitude: float + controls the amplitude of the dummy signal + sample_rate: float + sampling frequency + interest_freq: float + which frequency to generate the signal on + e.g. sample_rate = 5 and interest_freq = 1 + means the sampling frequency is 5 Hz + but the dominant power exists as 1 Hz + phase: float + phase of the signal + duration: float + duration of signal + + Returns: + ---------- + dummy_noise: np.ndarray + generated dummy signal + + References: + ---------- + Nguyen et. al. 2018: "Adaptive filtering of + physiological noises in fNIRS data" + Scarpa et. al. 2013: "A reference-channel based methodology + to improve estimation of event-related + hemodynamic response from fNIRS measurements" + Equation (4) + """ + + ts = _make_time_index(duration, sample_rate) + + dummy_noise = amplitude * np.sin(2 * np.pi * interest_freq * ts + phase) + + return dummy_noise + + +def motion_noise( + motion_amplitude: float, + motion_duration_mean: float, + sample_rate: float, + sample_duration: float, +) -> np.ndarray: + """ + Best guess of motor drift from Scarpa et al. 2013 + Attributes: + ---------- + motion_amplitude: float + slope of the motion drift + motion_duration_mean: float + mean duration of motion + sample_rate: float + sampling frequency + duration: float + duration of signal + + Returns: + ---------- + dummy_noise: np.ndarray + generated dummy signal + Notes: + -------- + from Scarpa et al 2013: + "In order to simulate artifacts (e.g., due to movements of the participant or + shifts of a source or a detector) short non-cyclic abrupt drifts were added in + 6 out of30 par- ticipants (it is the typical fraction noticed in our + experiment), at a random temporal position and with random amplitude, + and is repre- sented by the noise term r(t), different channel by channel. + + this is super vague, but just so we have an implementation: + - let's pretend "non cyclic abrupt drift" is a linear trend with some slope + - let's pretend "random temporal position" is interval based on + uniform start and truncated normal duration (truncated at 0) + with parametric mean and constant coeff of variation=1" + + References: + ---------- + Scarpa et. al. 2013: "A reference-channel based methodology + to improve estimation of event-related + hemodynamic response from fNIRS measurements" + p.109 + + """ + times = _make_time_index(sample_duration, sample_rate) + motion_start = np.random.randint(low=0, high=len(times)) + + # duration with constant coeff of variation + motion_duration = truncnorm.rvs( + loc=motion_duration_mean * sample_rate, + scale=motion_duration_mean * sample_rate, + a=0, + b=np.inf, + ) + + motion_times = _make_time_index(motion_duration, sample_rate) + + # in case we picked a motion duration greater than the window length, + # it's still valid, but we truncate + motion_times = motion_times[ + : min(motion_start + len(motion_times), len(times) - motion_start) + ] + + noise = motion_times * motion_amplitude + out = np.zeros_like(times) + out[motion_start : (motion_start + len(motion_times))] = noise + return out diff --git a/signal_processing/synthetic_data/tests/__init__.py b/signal_processing/synthetic_data/tests/__init__.py new file mode 100644 index 000000000..860ac27c6 --- /dev/null +++ b/signal_processing/synthetic_data/tests/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. diff --git a/signal_processing/synthetic_data/tests/test_additive_synth_dataset_variant.py b/signal_processing/synthetic_data/tests/test_additive_synth_dataset_variant.py new file mode 100644 index 000000000..7d064ef45 --- /dev/null +++ b/signal_processing/synthetic_data/tests/test_additive_synth_dataset_variant.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import unittest + +import numpy as np +from ..additive_synth_dataset_variant import ( + AdditiveSynthDatasetVariant, + generate_synth_data, + hrf_scale_from_channel_dists, +) + + +sample_rate = 10 +trial_length = 26 +hrf_scale = 1 + + +class AdditiveSyntheticDatasetVariantTest(unittest.TestCase): + @unittest.skip("only for manual local testing") + def test_generate_synth_data_one_trial(self): + + trial = generate_synth_data(hrf_scale, sample_rate, trial_length) + self.assertEqual(len(trial), trial_length * sample_rate) + + def test_generate_synth_data_multiple_channels(self): + class_labels = ["0", "1", "2"] + n_channels = 9 + hrf_scale_for_cond = np.zeros((n_channels, len(np.unique(class_labels)))) + hrf_scale_for_cond[0:2, 0] = 0.8 # first 10 channels responsive to cond1 + hrf_scale_for_cond[3:5, 1] = 0.7 # second 10 channels responsive to cond2 + hrf_scale_for_cond[6:8, 2] = 0 # second 10 channels responsive to cond3 + n_trials = 10 + trial_length = 26 + channel_dists = [10, 10, 10, 20, 20, 20, 30, 30, 30] + sample_rate = 10 + data = AdditiveSynthDatasetVariant( + n_trials, + trial_length, + channel_dists, + class_labels, + hrf_scale_for_cond, + sample_rate, + ) + + X = np.zeros( + (data.n_trials, data.n_channels, int(trial_length * sample_rate)) + ) # output data + + hrf_scales_from_distance = hrf_scale_from_channel_dists(channel_dists) + + for trial in range(n_trials): + for label in range(len(class_labels)): + for channel, hrf_scale_from_distance in enumerate( + hrf_scales_from_distance + ): + hrf_scale_final = ( + hrf_scale_from_distance * hrf_scale_for_cond[channel, label] + ) + X[trial, channel, :] = generate_synth_data( + hrf_scale_final, data.sample_rate, data.trial_length + ) + + # check dimensions for returned data, should be similar to output data (X) + self.assertEqual(X.shape, (10, 9, 260)) diff --git a/signal_processing/synthetic_data/tests/test_dummy_generator.py b/signal_processing/synthetic_data/tests/test_dummy_generator.py new file mode 100644 index 000000000..2a42991c7 --- /dev/null +++ b/signal_processing/synthetic_data/tests/test_dummy_generator.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import unittest + +import numpy as np +from colorednoise import powerlaw_psd_gaussian +from ..dummy_generator import ( + _make_time_index, + dummy_physiological_noise, + motion_noise, + simulated_hemodynamics, +) +from mne.time_frequency import psd_array_multitaper +from scipy.signal import savgol_filter + + +class DummyGeneratorTest(unittest.TestCase): + def setUp(self): + np.random.seed(42) + + def test_time_index(self): + + with self.assertRaises(RuntimeError): + _make_time_index(0.001, 100) + + time_index = _make_time_index(1, 1000) + + self.assertTrue(np.allclose(time_index, np.arange(0, 1, 0.001))) + + time_index = _make_time_index(5, 50) + self.assertEqual(time_index.shape[0], 50 * 5) + + def test_consistent_behavior(self): + # duration and sample_rate should work the same way in all the generators + + sample_rate = 50 + duration = 1 + n_samples = duration * sample_rate + sim_Hb = simulated_hemodynamics( + amplitude=1, sample_rate=sample_rate, duration=duration + ) + + physio_noise = dummy_physiological_noise( + amplitude=4e-7, + sample_rate=sample_rate, + interest_freq=1, + phase=np.pi / 4, + duration=duration, + ) + + measurement_noise = powerlaw_psd_gaussian(exponent=1.0, size=n_samples) + + self.assertEqual(sim_Hb.shape, physio_noise.shape) + self.assertEqual(sim_Hb.shape, measurement_noise.shape) + + def test_sim_hemodynamics(self): + duration = 30 + sample_rate = 50 + + # Create simulated hemodynamics + + sim_Hb1 = simulated_hemodynamics( + amplitude=1, sample_rate=sample_rate, duration=duration + ) + + sim_Hb2 = simulated_hemodynamics( + amplitude=1, sample_rate=sample_rate * 2, duration=duration + ) + + # check that peak is basically 5s + ts1 = _make_time_index(duration, sample_rate) + ts2 = _make_time_index(duration, sample_rate * 2) + self.assertAlmostEqual(np.abs(ts1[np.argmax(sim_Hb1)] - 5.0), 0) + self.assertAlmostEqual(np.abs(ts2[np.argmax(sim_Hb2)] - 5.0), 0) + + def test_physiological_noise(self): + duration = 1 + sample_rate = 1000 + interest_freq = 1 + + sim_Hb = simulated_hemodynamics( + amplitude=1, sample_rate=sample_rate, duration=duration + ) + # Simulated cardiac noise + cardiac_wave = dummy_physiological_noise( + amplitude=4e-7, + sample_rate=sample_rate, + interest_freq=interest_freq, + phase=np.pi / 4, + duration=duration, + ) + # Check dimensions + self.assertEqual(sim_Hb.shape, cardiac_wave.shape) + + # Calculate the PSD and make sure the peak is in 1 Hz + # calculate multitaper PSD + psds, freqs = psd_array_multitaper(cardiac_wave, sample_rate, n_jobs=12) + power_log10 = np.log10(psds) + ind_of_peak = np.unravel_index( + np.argmax(power_log10, axis=None), power_log10.shape + )[0] + self.assertAlmostEqual(freqs[ind_of_peak], interest_freq) + + cardiac_wave_double_freq = dummy_physiological_noise( + amplitude=4e-7, + sample_rate=sample_rate * 2, + interest_freq=interest_freq, + phase=np.pi / 4, + duration=duration, + ) + + self.assertTrue(np.allclose(cardiac_wave, cardiac_wave_double_freq[::2])) + + def test_pink_noise(self): + beta = 1 + sample_rate = 500 + duration = 2 + n_samples = duration * sample_rate + y = powerlaw_psd_gaussian(exponent=beta, size=n_samples) + # calculate the PSD + psds, freqs = psd_array_multitaper(y, sample_rate, n_jobs=12) + + # after enough smoothing on PSD, + # pink noise should follow smooth 1/f curve + # thus low freq power > high freq power + psds_smooth = savgol_filter(psds, 101, 3) + low_freq_power = np.median(psds_smooth[:10]) + med_freq_power = np.median(psds_smooth[80:110]) + high_freq_power = np.median(psds_smooth[400:]) + self.assertGreaterEqual(low_freq_power, med_freq_power) + self.assertGreaterEqual(med_freq_power, high_freq_power) + + # make sure behavior is same if we change sampling frequency + # TODO: make this a slightly more robust test + y_double_freq = powerlaw_psd_gaussian(exponent=beta, size=2 * n_samples) + psds, freqs = psd_array_multitaper(y_double_freq, sample_rate, n_jobs=12) + + psds_smooth = savgol_filter(psds, 101, 3) + low_freq_power = np.median(psds_smooth[:10]) + med_freq_power = np.median(psds_smooth[80:110]) + high_freq_power = np.median(psds_smooth[400:]) + self.assertGreaterEqual(low_freq_power, med_freq_power) + self.assertGreaterEqual(med_freq_power, high_freq_power) + + def test_motion_noise(self): + duration = 20 + sample_rate = 5000 + + # this conveniently checks that we don't throw if duration + # exceeds window ize + noise = motion_noise( + motion_amplitude=3, + motion_duration_mean=0.5, + sample_rate=sample_rate, + sample_duration=duration, + ) + + # the nonzero (noise) part should have slope \approx motion_amplitude + # but this is not a tight test since we get a random chunk + nonzero = noise[noise > 0] + slope = (nonzero[-1] - nonzero[0]) / len(nonzero) * sample_rate + self.assertAlmostEqual(slope, 3.0, places=2) + + sample_rate = 150 + noise = motion_noise( + motion_amplitude=1.75, + motion_duration_mean=0.5, + sample_rate=sample_rate, + sample_duration=duration, + ) + + nonzero = noise[noise > 0] + slope = (nonzero[-1] - nonzero[0]) / len(nonzero) * sample_rate + self.assertAlmostEqual(slope, 1.75, places=2) diff --git a/signal_processing/utils/data_view/_data_view_common_docs.py b/signal_processing/utils/data_view/_data_view_common_docs.py new file mode 100644 index 000000000..525f653f8 --- /dev/null +++ b/signal_processing/utils/data_view/_data_view_common_docs.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from ..doc.docfill import fill_in_docstring + +__all__ = ["data_view_common_docs"] + +data_view_common_docs = fill_in_docstring( + { + "size": """size: int + The size of each chunk + """, + "step": """step: int + How much to move by when taking a chunk. + """, + "ragged": """ragged: bool, optional, default False + If true, the last chunk could be smaller than the specified size. If + false, the last chunk that would have a number of element fewer than size + is discarded. + """, + } +) diff --git a/signal_processing/utils/data_view/array_utils.py b/signal_processing/utils/data_view/array_utils.py new file mode 100644 index 000000000..a32ecd98c --- /dev/null +++ b/signal_processing/utils/data_view/array_utils.py @@ -0,0 +1,667 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +import multiprocessing +from enum import Enum, auto +from itertools import count, product +from numbers import Number +from typing import ( + Any, + Callable, + Iterable, + Iterator, + Optional, + Union, + List, + Tuple, + Dict, + TypeVar, +) + +import numpy as np +from skimage.util import view_as_windows + +from ._data_view_common_docs import data_view_common_docs + +""" +This file holds simple utility functions for operations on numpy arrays. +""" + +A = TypeVar("A") +Index = Union[int, List, Tuple, slice, np.ndarray] +Indexer = Union[Index, Tuple[Index, ...]] + + +def is_index(value) -> bool: + return type(value) in (int, list, tuple, slice, np.ndarray) + + +def is_indexer(value) -> bool: + if type(value) is tuple: + return all(is_index(v) for v in value) + else: + return is_index(value) + + +def check_index(index: Index) -> "Index": + if not is_index(index): + raise IndexError( + "Index '{}' is not one of the valid index types: " + "[int, list, tuple, slice, np.ndarray]".format(index) + ) + else: + return index + + +def check_indexer(indexer: Indexer, ndim: int = 2) -> "Indexer": + if not is_indexer(indexer): + raise IndexError( + "Indexer '{}' is not one of the valid indexer types: " + "[Index, Tuple[Index, ...]]".format(indexer) + ) + if type(indexer) is tuple: + length = len(indexer) + else: + indexer, length = (indexer,), 1 + if not length == ndim and ndim is not None: + raise IndexError( + "Expected indexer of length '{}' but got indexer of length '{}'.".format( + ndim, len(indexer) + ) + ) + + return indexer + + +def is_one_axis_array(x: np.ndarray) -> bool: + """Evaluate whether the input array has multiple elements only along one axis + + Example: + + a.shape = (0,) -> True + a.shape = (5,) -> True + a.shape = (1,) -> True + a.shape = (5,1) -> True + a.shape = (1,5) -> True + a.shape = (5,2) -> False + a.shape = (1,1,5) -> True + a.shape = (2,1,5) -> False + a.shape = (1,1,1,1) -> True + + Args: + ---- + x: np.ndarray, input array to evaluate + + Returns: + ---- + Returns true if input array has multiple elements only along one axis + """ + + x_dims = x.shape + # case is when x is one dimensional + if len(x_dims) == 1: + return True + # case when x has only one element, dimension doesn't matter then + if x.size == 1: + return True + + # otherwise, x_dims should have none-1's at all except for one value + return np.sum(np.array(x_dims) > 1) == 1 + + +def d1_to_col(array: np.ndarray) -> np.ndarray: + """ + If 1D array, convert to column. + Otherwise return itself + """ + if len(array.shape) < 2: + return array.reshape((-1, 1)) + else: + # print("array not 1D, do nothing") + return array + + +def d1_to_row(array: np.ndarray) -> np.ndarray: + """ + If 1D array, convert to row. + Otherwise return itself + """ + if len(array.shape) < 2: + return array.reshape((1, -1)) + else: + # print("array not 1D, do nothing") + return array + + +def scalar_to_array(value) -> np.ndarray: + if np.isscalar(value): + return np.atleast_1d(value) + else: + return value + + +def scalar_to_slice_index(value: Index) -> "Index": + value = check_index(value) + if type(value) is int: + return slice(value, value + 1, 1) + else: + return value + + +def scalars_to_slice_indexer(value: Indexer, ndim: int = 2) -> "Indexer": + if not type(value) is tuple: + indexer = scalar_to_slice_index(check_indexer(value)) + else: + indexer = tuple(scalar_to_slice_index(v) for v in check_indexer(value)) + + return check_indexer(indexer, ndim=ndim) + + +def raise_1d_to_2d(array: np.ndarray) -> np.ndarray: + # Raise 1D array to 2D array with singleton dimension on right + assert array.ndim == 1 or array.ndim == 2 + if array.ndim == 1: + array = array[:, None] + + return array + + +def diagflat(v: np.ndarray, k: int = 0) -> np.ndarray: + """Wraps "np.diagflat" for >= 2D inputs.""" + # Transform flat array to diagonal matrix + v = ( + np.diagflat(v, k=k) + if v.ndim == 1 + else v[..., None] * np.eye(v.shape[-1], k=k)[(v.ndim - 1) * (None,)] + ) + + return v + + +def diagonal(v: np.ndarray, k: int = 0) -> np.ndarray: + """Wraps "np.diagonal" for >= 2D inputs.""" + # Transform diagonal matrix to flat array + v = np.diagonal(v, offset=k, axis1=-2, axis2=-1) + + return v + + +def upcast_ndim(x: np.ndarray, ndim: int) -> np.ndarray: + """Upcast array to dimensionality. + + NumPy matrix operations typically allow array broadcasting such that dimensions between two arrays can either + match or equal 1. + + Additionally, dimensions on the left may be missing from the left on one of two arrays, which would otherwise + satisfy the aforementioned constraints. In this case, the lower-rank array is treated as if it had singleton + dimensions to its left, which is equivalent to "upcasting". This allows for matrix operations on arrays of differing + ranks but otherwise compatible shapes. + + For more info, see https://numpy.org/doc/stable/user/basics.broadcasting.html. + + For example: np.ones((8, 4, 5, 1)) * np.ones((4, 1, 10)) -> np.ones((8, 4, 5, 10)) + + Args: + ---- + x: np.ndarray, input array to upcast + ndim: int, dimensionality to upcast to + + Returns: + ---- + Returns upcasted input array + """ + # Upcast array to dimensionality + shape = (ndim - x.ndim) * (1,) + x.shape + if not x.shape == shape: + x = x.reshape(shape) + + return x + + +def upcast_tile(x: np.ndarray, shape: Tuple) -> np.ndarray: + """Upcasts and tiles array to be match given shape. + + For NumPy operations that don't support flexible array broadcasting, it may also be necessary to ensure both arrays + have identical shapes. Singleton dimensions can simply be tiled to match the shape of the other array. + + For example: {np.ones((4, 5, 1)), (4, 1, 10)} -> np.ones((4, 5, 10)) + + + Args: + ---- + x: np.ndarray, input array to upcast and tile + + Returns: + ---- + Returns upcasted and tile input array + """ + # Upcast array then tile singleton dimensions + x = upcast_ndim(x, len(shape)) + if not x.shape == shape: + assert not any(not xs == s and not xs == 1 for xs, s in zip(x.shape, shape)) + x = np.tile(x, [1 if xs == s else s for xs, s in zip(x.shape, shape)]) + + return x + + +class SliceMaker(object): + """ + Utility to enable using slice syntax in functions + """ + + def __getitem__(self, item): + return item + + +""" +Object that can be used to generate slice object via +numpy style slicing notation. + +Ex. + ``` + >>> array = np.ones((5)) + >>> new_array = array[make_slice[0:3]] + >>> new_array + array([1., 1., 1.]) + ``` +Use this for the `select_x` methods in `TimeSeries` classes +""" +make_slice = SliceMaker() + + +""" +Function that act as masks that operate on numpy arrays, +convenience functions to serve as partial functions +""" + + +def equal_mask(x: np.ndarray, target: float) -> np.ndarray: + return x == target + + +def less_mask(x: np.ndarray, target: float) -> np.ndarray: + return x < target + + +def greater_mask(x: np.ndarray, target: float) -> np.ndarray: + return x > target + + +def le_mask(x: np.ndarray, target: float) -> np.ndarray: + return x <= target + + +def ge_mask(x: np.ndarray, target: float) -> np.ndarray: + return x >= target + + +class IntervalTypes(Enum): + CLOSED_OPEN = auto() # [a, b) + OPEN_CLOSED = auto() # (a, b] + CLOSED_CLOSED = auto() # [a, b] + OPEN_OPEN = auto() # (a, b) + + +def closed_open_range_mask( + x: np.ndarray, end_points: Tuple[float, float] +) -> np.ndarray: + return (x >= end_points[0]) & (x < end_points[1]) + + +def open_closed_range_mask( + x: np.ndarray, end_points: Tuple[float, float] +) -> np.ndarray: + return (x > end_points[0]) & (x <= end_points[1]) + + +def closed_closed_range_mask( + x: np.ndarray, end_points: Tuple[float, float] +) -> np.ndarray: + return (x >= end_points[0]) & (x <= end_points[1]) + + +def open_open_range_mask(x: np.ndarray, end_points: Tuple[float, float]) -> np.ndarray: + return (x > end_points[0]) & (x < end_points[1]) + + +@data_view_common_docs +def chunk( + arr: A, + size: int, + step: int = 1, + ragged: bool = False, + slice_function: Optional[Callable[[A, int, int], A]] = None, + len_function: Optional[Callable[[A], int]] = None, +) -> Iterator[A]: + """Chunk an array into `size` segments every `step` elements. + + Parameters + ---------- + arr: A + Any object on which the provided slice_function and len_function + operate on. + {size} + {step} + {ragged} + slice_function: Optional[Callable[[A, int, int], A]] + The function that can be used to slice the input array `arr` into + segments. If no slice function is specified, `__slice__` is called. + len_function: Optional[Callable[[A], int]] + The function that can be used to get the length of the input array + `arr`. If no length function is specified, `__len__` is called. + + Examples + -------- + >>> import numpy as np + >>> list(chunk(np.arange(0, 10), 4, 2)) + [array([0, 1, 2, 3]), + array([2, 3, 4, 5]), + array([4, 5, 6, 7]), + array([6, 7, 8, 9])] + + See Also + -------- + skimage.view_as_windows: Rolling window view of the input n-dimensional array + """ + # These default functions work for 1D numpy arrays + # as well as lists and tuples. + if slice_function is None: + + def slice_function(x, start, stop): + return x[start:stop] + + if len_function is None: + + def len_function(x): + return len(x) + + arr_len = len_function(arr) + + def compare_value(k): + return k if ragged else k + size - 1 + + for k in count(start=0, step=step): + if compare_value(k) >= arr_len: + break + else: + yield slice_function(arr, k, k + size) + + +# I stole these functions from https://stackoverflow.com/a/45555516 + + +def _unpacking_apply_along_axis( + all_args: Tuple[Callable, int, np.ndarray, Tuple, Dict] +) -> np.ndarray: + """ + Like numpy.apply_along_axis(), but with arguments in a tuple + instead. + + This function is useful with multiprocessing.Pool().map(): (1) + map() only handles functions that take a single argument, and (2) + this function can generally be imported from a module, as required + by map(). + """ + (func1d, axis, arr, args, kwargs) = all_args + return np.apply_along_axis(func1d, axis, arr, *args, **kwargs) + + +def parallel_apply_along_axis( + func1d: Callable, + axis: int, + arr: np.ndarray, + *args: Tuple, + pool: Optional["multiprocessing.Pool"] = None, + **kwargs: Dict +) -> np.ndarray: + """ + Like numpy.apply_along_axis(), but takes advantage of multiple + cores. + """ + # Effective axis where apply_along_axis() will be applied by each + # worker (any non-zero axis number would work, so as to allow the use + # of `np.array_split()`, which is only done on axis 0): + effective_axis = 1 if axis == 0 else axis + if effective_axis != axis: + arr = arr.swapaxes(axis, effective_axis) + + # Chunks for the mapping (only a few chunks): + chunks = [ + (func1d, effective_axis, sub_arr, args, kwargs) + for sub_arr in np.array_split(arr, multiprocessing.cpu_count()) + ] + + if pool is None: + pool = multiprocessing.Pool() + close_pool = True + else: + close_pool = False + + individual_results = pool.map(_unpacking_apply_along_axis, chunks) + + # Freeing the workers: + if close_pool: + pool.close() + pool.join() + + return np.concatenate(individual_results) + + +def grouped(iterable: Iterable[Any], n: int) -> Iterable[Iterable[Any]]: + "s -> (s0,s1,s2,...sn-1), (sn,sn+1,sn+2,...s2n-1), (s2n,s2n+1,s2n+2,...s3n-1), ..." + return zip(*[iter(iterable)] * n) + + +def nan_like(target: Union[Number, np.ndarray]) -> Union[Number, np.ndarray]: + if isinstance(target, Number): + return np.nan + elif isinstance(target, np.ndarray): + out = np.empty(target.shape) + out[:] = np.nan + return out + else: + raise ValueError("Target is not supported") + + +def window_inplace( + x: np.ndarray, window: Union[int, Tuple[int, int]], causal: bool = False +) -> np.ndarray: + """Wrapper around "skimage.util.view_as_windows" to support arbitray, causal or acausal window shifts. + + The window should either be an integer sample size or a tuple with inclusive left and right bounds relative to + the current sample (considered as 0). The latter allows for arbitrarily time-shifted and asymmetric windows. + + Windowing with a for loop and copy is slow and memory-hungry. For an arbitrary array with window W, the size + of the array in memory increases by a factor of W. For large arrays, this can easily fill up RAM. Instead of + copying, one can instead take a strided view into the array that simply repeats the indices to simulated the + windowed array in place without reallocating memory. + + Args: + ---- + x: np.ndarray, input array to window + window: int or tuple pair of ints, window size or inclusive window bounds relative to current sample + causal: bool, when using an integer window whether to distribute the samples causally or not + + Returns: + ---- + Returns windowed input array + """ + # Window in-place (without copying) using memory-efficient stride tricks + if type(window) is int: + window = ( + (-((window - 1) // 2), (window - 1) // 2 + (window - 1) % 2) + if not causal + else (-window + 1, 0) + ) + assert window[0] <= window[1] + x = np.pad( + x, + ((-min(window[0], 0), max(window[1] - 1, 0) + 1),) + (x.ndim - 1) * ((0, 0),), + mode="constant", + ) + w = view_as_windows(x, (window[1] - window[0] + 1,) + x.shape[1:])[ + None if window[0] < 0 else window[0] : None if window[1] > 0 else window[1] - 1 + ] + if x.ndim > 1: + w = w.squeeze(axis=tuple(range(1, x.ndim))) + w = w.transpose((0,) + tuple(range(2, w.ndim)) + (1,)) + + return w + + +def mix_array( + x: np.ndarray, + nrepeat: Optional[int] = None, + axis: Tuple[int, int] = (0, -1), + mixing: Optional[np.ndarray] = None, +) -> np.ndarray: + """Multiplies ND array by an independent 2D mixing matrix. + + Consider the simplest block-diagonal (mixing) matrix, the identity matrix, for example: + [[1, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 1]] + This constitutes no effective mixing or scaling. + + An arbitrary block-diagonal matrix contains sub-matrices (blocks) >= 1 x 1 placed along the diagonal as so: + [[A, 0, 0]] + [[0, B, 0]] + [[0, 0, c]] + + In practice, the blocks can be of varying (but always rectangular) shapes. + [[6, 4, 0, 0, 0, 0], + [2, 1, 0, 0, 0, 0], + [0, 0, 1, 1, 4, 0], + [0, 0, 3, 8, 5, 0], + [0, 0, 6, 7, 1, 0], + [0, 0, 0, 0, 0, 1]] + + However, unlike "scipy.linalg.block_diag," the mixing matrix need not be a block-digonal matrix, and can instead + mixing the array with arbitray mixing patterns. + + Args: + ---- + x: np.ndarray, input array to mix + nrepeat: int, number of times to effectively repeat array when using the default identity mixing matrix + axis: tuple, pair of axes to multiply mixing matrix with, with the latter being repeated 2nd dimension of mixing + matrix # of times + mixing: np.ndarray, mixing matrix to multiply input array with + + Returns: + ---- + Returns mixed input array + """ + # Parse axes + assert x.ndim >= 2 + assert len(axis) == 2 + axis = [ax % x.ndim for ax in axis] + if axis[0] == axis[1]: + x = x[(axis[1] + 1) * (slice(None, None, None),) + (None,)] + if nrepeat is None: + nrepeat = x.shape[axis[0]] + assert nrepeat == x.shape[axis[0]] or x.shape[axis[0]] == 1 or nrepeat == 1 + + # Apply mixing matrix + x = x[(axis[1] + 1) * (slice(None, None, None),) + (None,)] + if mixing is None: + mixing = np.eye(nrepeat) + else: + assert mixing.ndim == 2 + mixing = mixing[ + (min(axis) + int(axis[1] < axis[0])) * (None,) + + (slice(None, None, None),) + + (abs(axis[1] - axis[0]) - int(axis[1] < axis[0])) * (None,) + + (slice(None, None, None),) + ] + x = x * mixing + x = x.reshape(x.shape[: axis[1]] + (-1,) + x.shape[axis[1] + 2 :]) + + return x + + +def upcast_apply( + func: Callable[[Any], Any], + *args: Tuple, + narr: Optional[int] = None, + sub_ndim: int = 2, + **kwargs: Dict +) -> Optional[Any]: + """ + Allows normally *D-only (typically linear algebra) functions to support ND inputs. Many, but not all, NumPy and + SciPy functions support this natively for functions that normally operate on 2D inputs. + + Args: + ---- + func: callable, function to call on *D slices of upcasted input arrays + *args: variable arguments, arrays to provide function followed by any positional arguments + narr: int, number of positional arguments to consider to be arrays (otherwise inferred) + sub_ndim: int, dimensionality of array slices to provide function + **kwargs: variable named arguments, named keyword arguments to provide function + + Returns: + ---- + Returns mixed input array + """ + # Parse arrays + if narr is None: + arr = () + for i, arg in enumerate(args): + if isinstance(arg, np.ndarray): + arr += (arg,) + else: + args = args[i:] + break + else: + arr, args = args[:narr], args[narr:] + ndim = max(a.ndim for a in arr) + assert ndim >= sub_ndim + + # Cast inputs as needed then apply function + if ndim == 2: + out = func(*(arr + args), **kwargs) + else: + arr = tuple(upcast_ndim(a, ndim) for a in arr) + assert not any( + not all((s[0] == ss for ss in s or s[0] == 1 or ss == 1) for ss in s) + for s in zip(*[a.shape[:-sub_ndim] for a in arr]) + ) + samples = tuple( + max(sample) for sample in zip(*[a.shape[:-sub_ndim] for a in arr]) + ) + out = [ + func( + *[ + a[tuple(index % s for index, s, in zip(indices, a.shape))] + for a in arr + ], + **kwargs + ) + for indices in product(*[range(sample) for sample in samples]) + ] + if not isinstance(out[0], tuple): + out = np.array(out) + else: + for ( + i, + o, + ) in enumerate(zip(*out)): + if not o[0].shape: + out[i] = np.reshape(o, samples) + elif len(o[0]): + out[i] = np.reshape(o, samples + o[0].shape) + else: + shape, slc = zip( + *[ + (s, slice(None, None, None)) + if s + else (1, slice(0, 0, None)) + for s in o[0].shape + ] + ) + slc = len(samples) * (slice(None, None, None),) + slc + out[i] = np.empty(samples + shape)[slc] + + return out diff --git a/signal_processing/utils/doc/docfill.py b/signal_processing/utils/doc/docfill.py new file mode 100644 index 000000000..a06ead75e --- /dev/null +++ b/signal_processing/utils/doc/docfill.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +"""Filling out a docstring with identifier placeholders. + +The docstring filling decorator and associated functions are based off +scipy._lib.doccer, which has been moved to an internal location in scipy. +""" + +from typing import Any, Callable, Dict + +__all__ = ["fill_in_docstring"] + +AnyFunction = Callable[[Any], Any] + + +def fill_in_docstring(keywords: Dict[str, str]) -> Callable[[AnyFunction], AnyFunction]: + """Replace identifiers like `{ident}` in a docstring. + + Parameters + ---------- + keywords : Dict[str, str] + A mapping from identifiers to strings. + + Returns + ------- + decorator + A decorator that will replace f-string like identifiers in a docstring. + + Example + ------- + >>> common_doc = fill_in_docstring({'ident': 'This identifier '}) + >>> + >>> @common_doc + ... def f(x): + ... '''{ident} is common between functions!''' + ... pass + >>> + >>> f.__doc__ == "This identifier is common between functions!" + True + """ + + def _docfill(f): + f.__doc__ = f.__doc__.format( + **{ + identifier: description.rstrip() + for identifier, description in keywords.items() + } + ) + return f + + return _docfill diff --git a/signal_processing/utils/types/channel_info.py b/signal_processing/utils/types/channel_info.py new file mode 100644 index 000000000..5316577f7 --- /dev/null +++ b/signal_processing/utils/types/channel_info.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from typing import Any, Dict, List, Optional, Union + +import numpy as np + +from ..data_view.array_utils import is_one_axis_array + + +class ChannelInfo(object): + """ + This is a parent class to represent different sensor + modality's channel information. + + A `ChannelInfo` object can combine with a `TimeSeries` to form + `LabeledTimeSeires`, where each column of the `TimeSeries`' + `channel_data` can be described by each "row" of the + `ChannelInfo`. + + The individual "columns" of attributes can be accessed by + `my_channelinfo.myattr`. + + Slicing by different channels (or "rows") is supported, e.g. + `my_channelinfo[0:2]` will return a new `ChannelInfo` object + with the information about the first two channels only. + + All properties/attributes are readonly. + + Note that for each specific sensor modality, `ChannelInfo` should + be subclassed (e.g. `DOTChannelInfo`). The child class should + provide and instantiate the attributes in the constructor, and + provide its generation from `HardwareMetadata`, if applicable. + """ + + info_type: str + n_channels: int + + def __init__(self, info_type: str, n_channels: int = 0) -> None: + """ + Args: + --- + info_type: str, to describe the ChannelInfo + n_channels: int. Number of channels + """ + self.info_type = info_type + self.n_channels = n_channels + + def __repr__(self) -> str: + info_array = self.info_array + if len(info_array) == 0: + return f"{self.info_type}: Empty" + header = "|".join(info_array.dtype.names) + return f"{self.info_type}\n{header}\n{info_array}" + + @property + def info_array(self) -> np.recarray: + # Return the recarray formed from the ndarray attributes + # Preserving dtype + columns = [] + header = "" + for k, v in self.__dict__.items(): + if type(v) != np.ndarray: + continue + header += k + ", " + columns.append(v) + return np.core.records.fromarrays(columns, names=header) + + # All properties are readonly! And I don't want to make and decorate + # a new method for each property + def __setattr__(self, name: str, value: any) -> None: + if name in self.__dict__: + raise AttributeError( + f"Forbidden to set attributes for {self.__class__.__name__}" + ) + else: + self.__dict__[name] = value + + def __delattr__(self, name: str) -> None: + raise AttributeError( + f"Forbidden to delete attributes from {self.__class__.__name__}" + ) + + def __eq__(self, other: "ChannelInfo") -> bool: + same = True + for index, (value, other_value) in enumerate( + zip(self.__dict__.values(), other.__dict__.values()) + ): + if index < 2: + # ignore baseclass init arguments + continue + same &= np.array_equal(value, other_value) + + return same + + def __hash__(self) -> int: + hash_values = [] + for _, value in sorted(vars(self).items()): + if isinstance(value, np.ndarray): + hash_values.append(value.data.tobytes()) + else: + hash_values.append(value) + return hash(tuple(hash_values)) + + def __getitem__(self, key: Union[List, slice, np.ndarray, int]) -> Any: + func_args = [] + # Class attribute definition order is preserved iin __dict__ + # per PEP0520 (https://www.python.org/dev/peps/pep-0520/) + for index, value in enumerate(self.__dict__.values()): + if index < 2: + # ignore baseclass init arguments + continue + if type(value) is np.ndarray: + func_args.append(np.atleast_1d(value[key])) + else: + func_args.append(value) + return self.__class__(*func_args) + + def check_and_assign( + self, var_name: str, value: np.ndarray, var_dtype: np.dtype + ) -> None: + if value.dtype != var_dtype: + raise TypeError(f"{var_name}.dtype != {var_dtype}") + setattr(self, var_name, value) + + def __len__(self): + # len(channel_info) returns number of channels + return self.n_channels + + +class DOTChannelInfo(ChannelInfo): + # Inherited properties + # info_type: str + + # Properties + detector_idx: np.ndarray # int + source_idx: np.ndarray # int + distance: np.ndarray # float + source_x: np.ndarray # int + source_y: np.ndarray # int + source_z: np.ndarray # int + detector_x: np.ndarray # float + detector_y: np.ndarray # float + detector_z: np.ndarray # float + carrier_freq: np.ndarray # float + carrier_freq_idx: np.ndarray # int + wavelength: np.ndarray # float + subdevice_idx: np.ndarray # int + + # The entries here is {wavelength_idx:wavelength} + # such as {0 : 830.0, 1 : 685.0} + wavelength_table: Dict[int, float] + + # Include device name which is useful for any dependent processing + device_name: str + + def __init__( + self, + detector_idx: np.ndarray, + source_idx: np.ndarray, + distance: np.ndarray, + source_x: np.ndarray, + source_y: np.ndarray, + source_z: np.ndarray, + detector_x: np.ndarray, + detector_y: np.ndarray, + detector_z: np.ndarray, + carrier_freq: np.ndarray, + carrier_freq_idx: np.ndarray, + wavelength: np.ndarray, + subdevice_idx: np.ndarray, + wavelength_table: Dict[int, float], + device_name: Optional[str] = None, + ): + # args checking + # check all arguments are the same length + args = list(locals().values()) + args = [x for x in args if type(x) == np.ndarray] + + # make sure they are all 1D arrays + if not all(is_one_axis_array(x) for x in args): + raise ValueError("The inputs need to have all elements on the same axis") + # make sure they are the same length + args_len = np.array([x.size for x in args if type(x) == np.ndarray]) + if not np.all(args_len == args_len[0]): + raise ValueError("The inputs need to all have the same length!") + super().__init__("Flattened DOT", args_len[0]) + + # assign them to things + self.check_and_assign("detector_idx", detector_idx, "int") + self.check_and_assign("source_idx", source_idx, "int") + self.check_and_assign("distance", distance, "float") + self.check_and_assign("source_x", source_x, "float") + self.check_and_assign("source_y", source_y, "float") + self.check_and_assign("source_z", source_z, "float") + self.check_and_assign("detector_x", detector_x, "float") + self.check_and_assign("detector_y", detector_y, "float") + self.check_and_assign("detector_z", detector_z, "float") + self.check_and_assign("carrier_freq", carrier_freq, "float") + self.check_and_assign("carrier_freq_idx", carrier_freq_idx, "int") + self.check_and_assign("wavelength", wavelength, "float") + self.check_and_assign("subdevice_idx", subdevice_idx, "int") + self.wavelength_table = wavelength_table + self.device_name = device_name + + def __repr__(self): + return ( + f"Device={self.device_name}\n" + + super().__repr__() + + f"\nwavelength_table: {self.wavelength_table}" + ) + + +class Metric2ChannelInfo(ChannelInfo): + # Essentially the same as DOTChannelInfo, but now + # labels whether a channel corresponds to Metric1 or not + + # Inherited property + # info_type: str + + # Propertys + detector_idx: np.ndarray # int + source_idx: np.ndarray # int + distance: np.ndarray # float + source_x: np.ndarray # int + source_y: np.ndarray # int + source_z: np.ndarray # int + detector_x: np.ndarray # float + detector_y: np.ndarray # float + detector_z: np.ndarray # float + is_Metric1: np.ndarray # bool -- True if Metric1, False if HbR + subdevice_idx: np.ndarray # int + + # Include device name which is useful for any dependent processing + device_name: str + + def __init__( + self, + detector_idx: np.ndarray, + source_idx: np.ndarray, + distance: np.ndarray, + source_x: np.ndarray, + source_y: np.ndarray, + source_z: np.ndarray, + detector_x: np.ndarray, + detector_y: np.ndarray, + detector_z: np.ndarray, + is_Metric1: np.ndarray, + subdevice_idx: np.ndarray, + device_name: Optional[str] = None, + ): + # args checking + # check all arguments are the same length + args = list(locals().values()) + args = [x for x in args if type(x) == np.ndarray] + + # make sure they are all 1D arrays + if not all(is_one_axis_array(x) for x in args): + raise ValueError("The inputs need to have all elements on the same axis") + # make sure they are the same length + args_len = np.array([x.size for x in args if type(x) == np.ndarray]) + if not np.all(args_len == args_len[0]): + raise ValueError("The inputs need to all have the same length!") + super().__init__("Flattened Metric2 data", args_len[0]) + + # assign them to things + self.check_and_assign("detector_idx", detector_idx, "int") + self.check_and_assign("source_idx", source_idx, "int") + self.check_and_assign("distance", distance, "float") + self.check_and_assign("source_x", source_x, "float") + self.check_and_assign("source_y", source_y, "float") + self.check_and_assign("source_z", source_z, "float") + self.check_and_assign("detector_x", detector_x, "float") + self.check_and_assign("detector_y", detector_y, "float") + self.check_and_assign("detector_z", detector_z, "float") + self.check_and_assign("is_Metric1", is_Metric1, "bool") + self.check_and_assign("subdevice_idx", subdevice_idx, "int") + self.device_name = device_name + + def __repr__(self): + return f"Device={self.device_name}" + super().__repr__() + + +class StringChannelInfo(ChannelInfo): + """ + This is to generate a generic ChannelInfo class that specify column names of a + a time series, To be added + """ + + column_names: Union[List[str], np.array] + + def __init__(self, column_names: Union[List[str], np.array]) -> None: + if type(column_names) is list: + column_names = np.array(column_names, dtype="U32") + if not is_one_axis_array(column_names): + raise ValueError("column_names must be 1D") + super().__init__("column_names", len(column_names)) + self.check_and_assign("column_names", column_names, "U32") diff --git a/signal_processing/utils/types/time_series.py b/signal_processing/utils/types/time_series.py new file mode 100644 index 000000000..9ab42c6c5 --- /dev/null +++ b/signal_processing/utils/types/time_series.py @@ -0,0 +1,711 @@ +#!/usr/bin/env python3 +# Copyright 2004-present Facebook. All Rights Reserved. + +from typing import Callable, Union, List, Tuple + +import h5py +import numpy as np + +from ..data_view.array_utils import Index, scalars_to_slice_indexer +from .channel_info import ChannelInfo, StringChannelInfo + + +def is_valid_timestamps(timestamps: np.ndarray) -> bool: + try: + length = len(timestamps) + except TypeError: + length = 0 + if length == 1: + return True + if not timestamps.ndim == 1: + return False + return np.all(np.diff(timestamps) > 0) + + +def check_timestamps_and_data(timestamps: np.ndarray, data: np.ndarray) -> bool: + if not is_valid_timestamps(timestamps): + print("timestamps needs to be 1D, flat and monotonically increasing") + return False + + if data.ndim != 2: + print("data needs to be a 2D array") + return False + + if len(timestamps) != len(data): + print("timestamps and data need the same number of samples") + return False + + return True + + +def match_data_shape_to_timestamps( + timestamps: np.ndarray, data: np.ndarray +) -> np.ndarray: + """ + Assume `timestamps` come from a column vector, and `data` come from + a [time, channels] array. + + Sliced `data` can lose dimension information -- restore it here to match + the time points + """ + if not is_valid_timestamps(timestamps): + print("timestamps need to be 1D, monotonically increasing") + + time_points = len(timestamps) + if data.size < time_points: + raise ValueError("Impossible to match data with timestamps") + if len(data) == time_points: + return data + return data.reshape((time_points, -1)) + + +class HardwareMetadata(object): + """ + Represents the hardware specification for a given recording. + """ + pass + + +class TimeSeries(object): + """ + TimeSeries represents time series data (duh), with core properties + `TimeSeries.timestamps` and `TimeSeries.channel_data`. + + A useful model is to think about this object as a 2D array, with + rows increasing in time. The first column is the `timestamps`, and the + other columns are different channels. This representation can be accessed + through the `TimeSeries.time_series` property/method. + + `TimeSeries` enforces the invariant that time must increase monotonically, + and the timestamps and channel_data must have the same number of time points. + Setting either `timestamps` or `channel_data` property that violates this + will result in errors. + + `TimeSeries` supports setting and selecting data for given time + points (`select_time`), channels (`select_channels`), or combination of + both (`select_channel_data_at_time`), with syntax similar to + numpy array basic indexing. + (https://numpy.org/doc/stable/user/basics.indexing.html#basics-indexing) + + Slicing syntax can be achieved with the help of `array_utils.SliceMaker` class. + + Specifically, `select_time` can be thought of as selecting rows, + `select_channels` can be thought of as seleting columns of the `channel_data`, + therefore 1D numpy array indexing applies. + + Functions can be applied to `channel_data` (`transform_channel_data`), the returned + type (`TimeSeries` or numpy array) will differ depending on the type of transformation. + """ + + _timestamps: np.ndarray + _channel_data: np.ndarray + + def __init__(self, timestamps: np.ndarray, channel_data: np.ndarray) -> None: + """ + timestamps: 1D array with length equal to time + channel_data: 2D array of shape [time, channels] + """ + # In case we have a single number here + timestamps = np.atleast_1d(timestamps) + channel_data = np.atleast_1d(channel_data) + if check_timestamps_and_data(timestamps, channel_data): + self._timestamps = timestamps + self._channel_data = channel_data + else: + raise ValueError("timestamps and channel_data not compatible") + + def __repr__(self): + return "Time | Channels...\n" + str(self.time_series) + + def __len__(self): + return self.shape[0] + + def __eq__(self, other: "TimeSeries") -> bool: + return np.array_equal(self.timestamps, other.timestamps) and np.array_equal( + self.channel_data, other.channel_data + ) + + def __getitem__(self, indexer: Tuple[Index, Index]) -> "TimeSeries": + """ + Return new TimeSeries with given time and channel selection. Syntax is the same + as numpy array indexing for a 2D array of shape [n_times, n_channels] except + that scalar indexing returns a TimeSeries with singleton dimensions to preserve + data type. + + The `timestamps` and `channel_data` attributes of the returned + object are views of the original TimeSeries' attrributes. Therefore + modifications directly to the attributes via the property-getter can result + in unwanted side-effects (in which case make a deepcopy before proceeding!) + + Ex: + ``` + >>> ts = TimeSeries(np.arange(4), np.arange(8).reshape((4, -1))) + >>> ts.channel_data + array([[0, 1], + [2, 3], + [4, 5], + [6, 7]]) + >>> ts2 = ts[0:3, :] + >>> ts2.channel_data + array([[0, 1], + [2, 3], + [4, 5]]) + >>> # scalar casts to singleton + >>> ts2 = ts[0, :] + >>> ts2.channel_data + array([[0, 1]]) + >>> # will throw an error... + >>> ts.channel_data[0:2, :] = 1 + >>> ts.channel_data + array([[1, 1], + [1, 1], + [4, 5], + [6, 7]]) + >>> ts2.channel_data + array([[1, 1], + [1, 1], + [4, 5]]) + # ts2's channel_data has also changed due to shared reference + ``` + """ + time_indexer, channel_indexer = scalars_to_slice_indexer(indexer) + return self.__class__( + self.timestamps[time_indexer], + self.channel_data[time_indexer, channel_indexer], + ) + + @property + def shape(self): + return self._channel_data.shape + + @classmethod + def from_array(cls, array: np.ndarray) -> "TimeSeries": + """ + Assume first column is time, all other is data + """ + assert len(array.shape) == 2 and array.shape[1] >= 2 + return cls(array[:, 0], array[:, 1:]) + + @classmethod + def load(cls, filepath: str) -> "TimeSeries": + with h5py.File(filepath, "r") as f: + timestamps = f.get("timestamps").value + channel_data = f.get("channel_data").value + return cls(timestamps, channel_data) + + def save(self, filepath: str) -> None: + with h5py.File(filepath, "w") as f: + f.create_dataset("timestamps", data=self._timestamps) + f.create_dataset("channel_data", data=self._channel_data) + + @property + def timestamps(self) -> np.ndarray: + return self._timestamps + + @timestamps.setter + def timestamps(self, new_timestamps: np.ndarray) -> None: + """ + Set the timestamps to new_timestamps, must be same length as before + """ + if not is_valid_timestamps(new_timestamps): + raise ValueError( + "new_timestamps is either not 1D or not monotonically increasing" + ) + elif len(self._timestamps) != len(new_timestamps): + raise ValueError("new_timestamps has the incorrect shape!") + self._timestamps = new_timestamps + + @property + def channel_data(self) -> np.ndarray: + return self._channel_data + + @channel_data.setter + def channel_data(self, value: np.ndarray) -> None: + """ + Set the channel_data, must be the same length as before + """ + if len(value.shape) != 2: + raise ValueError("value needs to be 2-dimensional") + elif len(value) != len(self._channel_data): + raise ValueError("value has wrong number of time points") + self._channel_data = value + + @property + def sample_rate(self) -> float: + return 1.0 / np.mean(np.diff(self._timestamps)) + + @property + def time_series(self) -> np.ndarray: + return np.hstack((self.timestamps.reshape((-1, 1)), self._channel_data)) + + def select_channels(self, channel_selection: Index) -> "TimeSeries": + """ + Return new TimeSeries with the selected channels only + + Args: + --- + `channel_selection` : Expression used to do basic indexing of + a numpy array in a single dimension. + + - List, Tuple, np.ndarray type inputs can be integer and boolean + masks. + - Slicing operation (e.g. array[a:b] syntax) can be achieved through + + ``` + from data_view.array_utils import SliceMaker + make_slice = SliceMaker() + # select all but the last channel + time_series.select_channels(make_slice[0:-2]) + ``` + + Returns: + --- + A new `TimeSeries` objects with the selected channels. + """ + return self[:, channel_selection] + + def select_time(self, time_selection: Index) -> "TimeSeries": + """ + Return new TimeSeries with the selected time points only + + Args: + --- + `time_selection` : Expression used to do basic indexing of + a numpy array in a single dimension. + + - List, Tuple, np.ndarray type inputs can be integer and boolean + masks. + - Slicing operation (e.g. array[a:b] syntax) can be achieved through + + ``` + from data_view.array_utils import SliceMaker + make_slice = SliceMaker() + # select all but last time point + time_series.select_time(make_slice[0:-2]) + ``` + + Returns: + --- + A new `TimeSeries` objects with the selected channels. + """ + return self[time_selection, :] + + def select_channel_data_at_time( + self, + time_selection: Index, + channel_selection: Index, + ) -> "TimeSeries": + """ + Return new TimeSeries with the selected time points and + selected channels only + + Args: + --- + - `time_selection`, `channel_selection` : + Expression used to do basic indexing of a numpy array in a single dimension. + + - List, Tuple, np.ndarray type inputs should be integers. If using boolean + masking, can run into errors: + + "IndexError: shape mismatch: indexing arrays could not be broadcast + together with shapes..." + + This is due to underlying numpy array indexing behavior, + see https://stackoverflow.com/a/30176382 + + - Slicing operation (e.g. array[a:b] syntax) can be achieved through + `SliceMaker` similar to `select_time()` and `select_channels`. + + Example: + ``` + from data_view.array_utils import SliceMaker + make_slice = SliceMaker() + # select all but last time point + time_series.select_channel_data_at_time( + make_slice[0:5], # select first 6 time points + [0, 1, -1], # select first 2 and last channel + ) + """ + return self[time_selection, channel_selection] + + def transform_channel_data( + self, func: Callable[[np.ndarray], np.ndarray] + ) -> "TimeSeries": + """ + Apply `func` on `channel_data`. + + If the transformed `channel_data` is 2D and has the same number + of rows as before, a new `TimeSeries` with the transformed + data and original `timestamps` is returned. + + This means if the `func` does not change the number of time points, + a new `TimeSeries` is returned. + + Otherwise, the transformed `channel_data` is returned as an np.ndarray + + Ex. + ``` + new_ts = ts.transform_channel_data(partial(scipy.stats.zscore, axis=0)) + ``` + returns a TimeSeries with the channels zscored in time. + + ``` + new_ts = ts.transform_channel_data(partial(np.mean, axis=1, keepdims=True)) + ``` + returns a TimeSeries with channel_data equal to the mean of `ts`'s channels. + + ``` + new_ts = ts.transform_channel_data(lambda x: np.vstack((x, x))) + ``` + returns an np.ndarray equal to `np.vstack((ts.channel_data, ts.channel_data))` + + ``` + new_ts = ts.transform_channel_data(lambda x: np.hstack((x, x))) + ``` + returns a TimeSeries with channel_data equal to + `np.hstack((ts.channel_data, ts.channel_data))` + + """ + + out = func(self._channel_data) + if len(out.shape) < 2 or len(out) != len(self._timestamps): + return out + else: + return self.__class__(self._timestamps, out) + + +class LabeledTimeSeries(TimeSeries): + """ + Class to bundle TimeSeries with ChannelInfo object that + gives information about each channel. + + The attributes of the ChannelInfo can be referred to with + the same name from a LabeledTimeSeries. + + Ex. + + `lts` is a LabeledTimeSeries with the `channel_info` object: + + `lts = LabeledTimeSeries(timestamps, channel_data, channel_info)` + + The `detector_idx` property of `channel_info` can be referred to + by `lts.detector_idx`. + """ + + # Addition to TimeSeries base class + _channel_info: ChannelInfo + + """ + Methods inherited and not overridden from TimeSeries: + - sample_rate + """ + + def __init__( + self, + timestamps: np.ndarray, + channel_data: np.ndarray, + channel_info: ChannelInfo, + ) -> None: + # In case we have single number as timestamps and channel_data + timestamps = np.atleast_1d(timestamps) + channel_data = np.atleast_1d(channel_data) + + # Check channel_info' shape and channel_data match, + # and channel_info is right type + if not issubclass(type(channel_info), ChannelInfo): + raise TypeError("channel_info must be a ChannelInfo class") + if len(channel_data.shape) != 2: + raise ValueError("channel_data needs to have dimension [time, channels]") + if len(channel_info) != channel_data.shape[1]: + raise ValueError( + "channel_data should have num of cols equal to length of ChannelInfo" + ) + if not check_timestamps_and_data(timestamps, channel_data): + raise ValueError("timestamps and channel_data are not compatible") + + self._channel_info = channel_info + self._timestamps = timestamps + self._channel_data = channel_data + + # __getstate__ and __setstate__ need to be set in order to pickle LTS + # objects. Otherwise pickle attempts to call __getattr__ and runs into an + # infinite loop. # more information can be found here: + # https://stackoverflow.com/questions/50888391/pickle-of-object-with-getattr-method-in-python-returns-typeerror-object-no/50888571#50888571 + def __getstate__(self): + return vars(self) + + def __setstate__(self, state): + vars(self).update(state) + + # Auto-generate properties from the ChannelInfo + # `__getattr__` is only called if python can't find + # the attribute via other means + def __getattr__(self, name): + """ + Ex. `my_labeled_time_series.channel_info.channel_attribute` + is the same as `my_labeled_time_series.channel_attribute` + """ + return getattr(self._channel_info, name) + + def __repr__(self): + return ( + "LabeledTimeSeries (Time | Channels...)\n" + + str(self.time_series) + + "\nLabels\n" + + str(self._channel_info) + ) + + def __eq__(self, other: "LabeledTimeSeries") -> bool: + return super().__eq__(self, other) and self.channel_info == other.channel_info + + def __getitem__(self, indexer: Tuple[Index, Index]) -> "TimeSeries": + """ + Return new LabeledTimeSeries with given time and channel selection. Syntax + is the same as numpy array indexing for a 2D array of shape [n_times, n_channels] + except that scalar indexing returns a LabeledTimeSeries with singleton dimensions + to preserve data type. + + The `timestamps` and `channel_data` attributes of the returned + object are views of the original LabeledTimeSeries' attrributes. Therefore + modifications directly to the attributes via the property-getter can result + in unwanted side-effects (in which case make a deepcopy before proceeding!) + + Ex: + ``` + >>> ts = LabeledTimeSeries(np.arange(4), np.arange(8).reshape((4, -1)), channel_info) + >>> ts.channel_data + array([[0, 1], + [2, 3], + [4, 5], + [6, 7]]) + >>> ts2 = ts[0:3, :] + >>> ts2.channel_data + array([[0, 1], + [2, 3], + [4, 5]]) + >>> # scalar casts to singleton + >>> ts2 = ts[0, :] + >>> ts2.channel_data + array([[0, 1]]) + >>> # will throw an error... + >>> ts.channel_data[0:2, :] = 1 + >>> ts.channel_data + array([[1, 1], + [1, 1], + [4, 5], + [6, 7]]) + >>> ts2.channel_data + array([[1, 1], + [1, 1], + [4, 5]]) + # ts2's channel_data has also changed due to shared reference + ``` + """ + time_indexer, channel_indexer = scalars_to_slice_indexer(indexer) + return self.__class__( + self.timestamps[time_indexer], + self.channel_data[time_indexer, channel_indexer], + self.channel_info[channel_indexer], + ) + + @classmethod + def from_array( + cls, array: np.ndarray, channel_info: ChannelInfo + ) -> "LabeledTimeSeries": + """ + Assume first column is time, all other is data + """ + assert len(array.shape) == 2 and array.shape[1] >= 2 + return cls(array[:, 0], array[:, 1:], channel_info) + + @classmethod + def from_hardware( + cls, + timestamps: np.ndarray, + channel_data: np.ndarray, + hardware: HardwareMetadata, + channel_info: ChannelInfo, + ) -> "LabeledTimeSeries": + # Assume the ChannelInfo subclass implements `from_hardware` method + channel_info = channel_info.from_hardware(hardware) + return cls(timestamps, channel_data, channel_info) + + # Override load/save -- ChannelInfo has no load/save so.. + @classmethod + def load(cls, filepath: str) -> "LabeledTimeSeries": + raise NotImplementedError("LabeledTimeSeries.load is not implemented") + + def save(self, filepath: str) -> None: + raise NotImplementedError("LabeledTimeSeries.save is not implemented") + + @property + def channel_data(self) -> None: + return self._channel_data + + @channel_data.setter + def channel_data(self, value: np.ndarray) -> None: + """ + Set the channel_data, must be the same shape as before to be + consistent with channel_info + """ + if len(value.shape) != 2: + raise ValueError("value needs to be 2-dimensional") + elif value.shape != self._channel_data.shape: + raise ValueError( + "value needs to be the same shape as the original channel-data" + ) + self._channel_data = value + + @property + def channel_info(self): + return self._channel_info + + def select_channels(self, channel_selection: Index) -> "LabeledTimeSeries": + """ + Return new LabeledTimeSeries with selected channels, + timestamps is unchanged in the output. + + Args: + --- + - channel_selection: See `TimeSeries.select_channels` + """ + return self[:, channel_selection] + + def select_time(self, time_selection: Index) -> "LabeledTimeSeries": + """ + Return new LabeledTimeSeries with selected time points. + Both timestamps and channel_data will be different in the output + + Args: + --- + - time_selection: See `TimeSeries.select_time`. + """ + return self[time_selection, :] + + def select_channel_data_at_time( + self, + time_selection: Index, + channel_selection: Index, + ) -> "LabeledTimeSeries": + """ + Return new LabeledTimeSeries with selected channels, at the selected + time points. + Both timestamps and channel_data will be different in the output + + Args: See `TimeSeries.select_channel_data_at_time`. + """ + return self[time_selection, channel_selection] + + def transform_channel_data( + self, func: Callable[[np.ndarray], np.ndarray] + ) -> Union["LabeledTimeSeries", TimeSeries, np.ndarray]: + """ + Apply `func` on `channel_data`. + + The output's type depends on whether the supplied `func` changes the number + of rows (time points) and columns (channels) of `channel_data`. + + + | preserve channels | change channels + -------------|--------------------------|-------------------------- + preserve time| `LabeledTimeSeries` with | TimeSeries with new + | new channel_data, same | channel_data, same + | timestamps, same | timestamps, no + | channel_info | channel_info + -------------|---------------------------------------------------- + change time | new data as np.ndarray | new data as np.ndarray + + Ex. + ``` + new_lts = lts.transform_channel_data(partial(scipy.stats.zscore, axis=0)) + ``` + returns a LabeledTimeSeries with the channels zscored in time. + + ``` + new_lts = lts.transform_channel_data(partial(np.mean, axis=1)) + ``` + returns a TimeSeries with channel_data equal to the mean of `lts`'s channels. + The channel_info are lost in the process. + + ``` + new_lts = lts.transform_channel_data(lambda x: np.vstack((x, x))) + ``` + returns an np.ndarray equal to `np.vstack((ts.channel_data, ts.channel_data))` + + ``` + # ts.channel_data has shape (5,5) + new_lts = lts.transform_channel_data(lambda x: x[0:2, 0:2]) + ``` + returns an np.ndarray equal to `lts.channel_data[0:2, 0:2]` + """ + + out = func(self._channel_data) + if len(out.shape) < 2 or len(out) != len(self._timestamps): + # changes time + return out + elif out.shape[1] != len(self._channel_info): + # changes channel + return TimeSeries(self._timestamps, out) + else: + # preserve time and channel + return self.__class__(self._timestamps, out, self._channel_info) + + +class StringLabeledTimeSeries(LabeledTimeSeries): + """ + Class to handle simple labeling, e.g., just add the column names + """ + + # Addition to TimeSeries base class + # _timestamps: np.ndarray + # _channel_data: np.ndarray + _channel_info: StringChannelInfo + + """ + Methods inherited from LabeledTimeSeries: + - sample_rate + """ + + def __init__( + self, + timestamps: np.ndarray, + channel_data: np.ndarray, + channel_info: StringChannelInfo, + ) -> None: + timestamps = np.atleast_1d(timestamps) + channel_data = np.atleast_1d(channel_data) + # Check channel_info' shape and channel_data match, + # and channel_info is right type + if not issubclass(type(channel_info), ChannelInfo): + raise TypeError("channel_info must be a ChannelInfo class") + if len(channel_data.shape) != 2: + raise ValueError("channel_data needs to have dimension [time, channels]") + if len(channel_info) != channel_data.shape[1]: + raise ValueError( + "channel_data should have num of cols equal to length of ChannelInfo" + ) + if not check_timestamps_and_data(timestamps, channel_data): + raise ValueError("timestamps and channel_data are not compatible") + + self._channel_info = channel_info + self._timestamps = timestamps + self._channel_data = channel_data + + def from_hardware(self): + """ + Forget this inherited class + """ + print("not implemented, ignoring it") + pass + + def select_channels_by_set( + self, channel_set: Union[List[str], np.array] + ) -> "StringLabeledTimeSeries": + """ + An easier wrapper for select_channels, example usage + `time_series2 = time_series1.select_channels_by_set(['LEFT_INDEX'])` + Inputs: + channel_set, a list of channel names, or an np.array of strings + Output: StringLabeledTimeSeries + """ + return self[:, np.in1d(self.channel_info.column_names, channel_set)]