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:**
+
+
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(
-
-
-
-
- onLayout('TB')}>vertical layout
- onLayout('LR')}>horizontal layout
-
-
-
-
- Node
- Type
-
-
-
-
- {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.
+
+
+
+ Node
+ Edge
+
+
+
+
+
+
-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(
-
-
-
-
- onLayout('TB')}>vertical layout
- onLayout('LR')}>horizontal layout
-
-
-
-
- Node
- Type
-
-
-
-
- {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(
-
-
-
-
- onLayout('TB')}>vertical layout
- onLayout('LR')}>horizontal layout
-
-
-
-
- Node
- Type
-
-
-
-
- {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
+
+
+ You need to enable JavaScript to run this app.
+
+
+
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 (
+
+ is connecting
+
+ );
+ case WS_STATE.CONNECTED:
+ return (
+
+ disconnect
+
+ );
+ case WS_STATE.IS_DISCONNECTING:
+ return (
+
+ is disconnecting
+
+ );
+ case WS_STATE.DISCONNECTED:
+ return (
+
+ connect
+
+ );
+ }
+ }, [connection]);
+
+ return (
+
+
+
+
+
+ realtime
+
+ }
+ value="1"
+ />
+ mock
+ }
+ value="2"
+ />
+
+
+
+
+
+
+
+
+
+
+
+ Mock
+
+
+
+ Mock
+
+ {Object.entries(MOCK).map(([key, value]) => {
+ return (
+
+ {value}
+
+ );
+ })}
+
+
+
+
+
+
+ );
+};
+
+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)]