diff --git a/.gitignore b/.gitignore
index 2b7e9672a0f0c23298dbe55aa0e4058cd1b91d65..667cd7b8ef3bbafeed225a7e73efb4e844acc0d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+
+issue.md
 loginDetails.json
 package-lock.json
 
@@ -78,3 +80,6 @@ Sessionx.vim
 tags
 # Persistent undo
 [._]*.un~
+
+# Local Reacordings
+Recordings/*
diff --git a/app/App.tsx b/app/App.tsx
index bdb35ee292871071511a8f32d0a8313923fc3721..653da7883f4bcf1b82810ef3b133a17941e3e925 100644
--- a/app/App.tsx
+++ b/app/App.tsx
@@ -1,10 +1,8 @@
 import React from 'react';
 import { createMuiTheme, ThemeProvider } from '@material-ui/core';
-import { HashRouter as Router, Switch, Route } from 'react-router-dom';
-import routes from './routes';
-import LoginScreen from './containers/LoginScreen';
-import Dashboard from './containers/Dashboard';
-import { LoginDetailsProvider } from './context/LoginDetailsContext';
+import { HashRouter as Router } from 'react-router-dom';
+import TabContainer from './containers/TabContainer';
+import { TabProvider } from './context/TabContext';
 
 const theme = createMuiTheme({
   palette: {
@@ -15,6 +13,7 @@ const theme = createMuiTheme({
     primary: {
       light: '#769CFF',
       main: '#769CFF',
+      dark: '#404040', // Dark 1
       contrastText: '#FFFFFF',
     },
     text: {
@@ -24,13 +23,13 @@ const theme = createMuiTheme({
     },
     background: {
       default: '#404040', // Dark 2
-      paper: '#4B4B4B', // Dark 3
+      paper: '#404040', // Dark 1
     },
   },
   typography: {
     fontFamily: ['Raleway', 'Arial'].join(','),
     button: {
-      textTransform: 'capitalize',
+      textTransform: 'initial',
     },
     subtitle2: {
       color: 'rgba(255, 255, 255, 0.7)',
@@ -59,18 +58,11 @@ export default function App() {
   return (
     <div className="content">
       <ThemeProvider theme={theme}>
-        <LoginDetailsProvider>
-          <Router>
-            <Switch>
-              <Route exact path={routes.login}>
-                <LoginScreen />
-              </Route>
-              <Route path={routes.dashboard}>
-                <Dashboard />
-              </Route>
-            </Switch>
-          </Router>
-        </LoginDetailsProvider>
+        <Router>
+          <TabProvider>
+            <TabContainer />
+          </TabProvider>
+        </Router>
       </ThemeProvider>
     </div>
   );
diff --git a/app/QueryOutput.ts b/app/QueryOutput.ts
index feaec6bacdec06713e081dcac2210de9440a22b6..9cb62e1427167d44a04799a5df47e67291d28f4b 100644
--- a/app/QueryOutput.ts
+++ b/app/QueryOutput.ts
@@ -3,4 +3,5 @@ import { Result } from '../backend/utils/mysql';
 export interface QueryOutput {
   results?: Result;
   error?: undefined;
+  columnWidths?: number[];
 }
diff --git a/app/app.global.css b/app/app.global.css
index 55a420d6b0ba5e7e880e01f2ca765cf33ff7adb4..72a70f846a7f79920c91dcdcb6a285573426d03b 100644
--- a/app/app.global.css
+++ b/app/app.global.css
@@ -36,7 +36,7 @@ body #root {
 
 /* Track */
 ::-webkit-scrollbar-track {
-  background: #404040;
+  background: #4b4b4b;
   border-radius: 8px;
 }
 
diff --git a/app/components/ErrorView.tsx b/app/components/ErrorView.tsx
deleted file mode 100644
index 2a4e789a77abcedbf08a50bfe71cd81818a1ff2b..0000000000000000000000000000000000000000
--- a/app/components/ErrorView.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { Paper, Typography } from '@material-ui/core';
-import { makeStyles } from '@material-ui/core/styles';
-import React, { useContext } from 'react';
-import { RecordingContext } from '../context/RecordingContext';
-
-export default function ErrorView() {
-  const recordingContext = useContext(RecordingContext);
-  const error = recordingContext?.activeRecording?.error;
-
-  const useStyles = makeStyles((theme) => ({
-    content: {
-      display: 'flex',
-      flexFlow: 'column nowrap',
-      padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
-    },
-  }));
-
-  const classes = useStyles();
-
-  return (
-    <div>
-      {error && (
-        <Paper className={classes.content} elevation={1}>
-          <Typography variant="h5">Errors</Typography>
-          <Typography
-            variant="subtitle1"
-            style={{
-              whiteSpace: 'pre-line',
-            }}
-            color="error"
-          >
-            {error}
-          </Typography>
-        </Paper>
-      )}
-    </div>
-  );
-}
diff --git a/app/components/IDInfo.tsx b/app/components/IDInfo.tsx
deleted file mode 100644
index 1fe032c8c0dce173baf80046c5bd97c5a4e537b9..0000000000000000000000000000000000000000
--- a/app/components/IDInfo.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import { makeStyles, Paper, Typography } from '@material-ui/core';
-import SqlManagerSingleton from '../../backend/recorder/SqlManagerSingleton';
-
-const useStyles = makeStyles((theme) => ({
-  wrapper: {
-    padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
-  },
-}));
-
-export default function IDInfo() {
-  const manager = SqlManagerSingleton.getInstance();
-
-  const classes = useStyles();
-
-  return (
-    <Paper elevation={1} className={classes.wrapper}>
-      <Typography variant="subtitle2">
-        {`Runner ConnectionID: ${manager.runner?.connectionID} Runner ThreadID: ${manager.runner?.threadID}`}
-      </Typography>
-      <Typography variant="subtitle2">
-        {`Monitor ConnectionID: ${manager.monitor?.connectionID} Monitor ThreadID: ${manager.monitor?.threadID}`}
-      </Typography>
-      <Typography variant="subtitle2">
-        {`Agent ConnectionID: ${manager.agent?.connectionID} Agent ThreadID: ${manager.agent?.threadID}`}
-      </Typography>
-    </Paper>
-  );
-}
diff --git a/app/components/TreeViewExplainAnalyze.tsx b/app/components/TreeViewExplainAnalyze.tsx
deleted file mode 100644
index 2d80da70e94b33a2379ac5adad8c9e5dab9a589e..0000000000000000000000000000000000000000
--- a/app/components/TreeViewExplainAnalyze.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React, { useContext } from 'react';
-import { makeStyles } from '@material-ui/core/styles';
-import TreeView from '@material-ui/lab/TreeView';
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
-import ChevronRightIcon from '@material-ui/icons/ChevronRight';
-import TreeItem from '@material-ui/lab/TreeItem';
-import { RecordingContext } from '../context/RecordingContext';
-import { ExplainAnalyzeNode } from '../../backend/data-processor/types/ExplainAnalyzeNode';
-
-const useStyles = makeStyles({
-  root: {
-    flexGrow: 1,
-  },
-});
-
-export default function RecursiveTreeView() {
-  const recordingContext = useContext(RecordingContext);
-  const data = recordingContext?.activeRecording?.explainAnalyzeTree;
-
-  const classes = useStyles();
-
-  const RenderTree = (nodes: ExplainAnalyzeNode) => (
-    <TreeItem key={nodes.id} nodeId={nodes.id} label={nodes.name}>
-      {Array.isArray(nodes.children)
-        ? nodes.children.map((node) => RenderTree(node))
-        : null}
-    </TreeItem>
-  );
-
-  const RenderTreeWithoutRoot = (root?: ExplainAnalyzeNode) => {
-    // Renders only the children of root
-    const elements: JSX.Element[] = [];
-    root?.children.forEach((child) => {
-      elements.push(RenderTree(child));
-    });
-    return elements;
-  };
-
-  // TODO: Find dynamic solution to defaultExpanded
-  return (
-    <TreeView
-      className={classes.root}
-      defaultCollapseIcon={<ExpandMoreIcon />}
-      defaultExpanded={['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']}
-      defaultExpandIcon={<ChevronRightIcon />}
-    >
-      {[RenderTreeWithoutRoot(data)]}
-    </TreeView>
-  );
-}
diff --git a/app/components/ColorPicker.tsx b/app/components/dashboard/ColorPicker.tsx
similarity index 100%
rename from app/components/ColorPicker.tsx
rename to app/components/dashboard/ColorPicker.tsx
diff --git a/app/components/dashboard/ErrorView.tsx b/app/components/dashboard/ErrorView.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2417d04f24cbe2dbac973feda83b52161be1b34a
--- /dev/null
+++ b/app/components/dashboard/ErrorView.tsx
@@ -0,0 +1,52 @@
+import { Paper, Typography } from '@material-ui/core';
+import { makeStyles } from '@material-ui/core/styles';
+import React, { useContext } from 'react';
+import { TabContext } from '../../context/TabContext';
+
+const useStyles = makeStyles((theme) => ({
+  content: {
+    display: 'flex',
+    flexFlow: 'column nowrap',
+    padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
+  },
+}));
+
+export default function ErrorView() {
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const recordings = { ...tab?.recordings };
+  const error = recordings[tab?.activeRecordingID || '']?.error;
+
+  let errorMessage;
+  if (error?.info) {
+    const errorcode = JSON.stringify(error?.info?.code, undefined, 2);
+    const errormsg = JSON.stringify(error?.info?.msg, undefined, 2);
+    errorMessage = `Errorcode: ${errorcode}\n${errormsg}`;
+  } else if (error === 'cancelled') {
+    errorMessage = 'Query was cancelled';
+  } else {
+    errorMessage = String(error);
+  }
+
+  const classes = useStyles();
+
+  return (
+    <div>
+      {error && (
+        <Paper className={classes.content} elevation={1}>
+          <Typography variant="h5">Errors</Typography>
+          <Typography
+            variant="subtitle1"
+            style={{
+              whiteSpace: 'pre-line',
+            }}
+            color="error"
+          >
+            {errorMessage}
+          </Typography>
+        </Paper>
+      )}
+    </div>
+  );
+}
diff --git a/app/components/dashboard/GlobalSnackbar.tsx b/app/components/dashboard/GlobalSnackbar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..439ab11db2227a47e8e2f81f52431b051a14d09a
--- /dev/null
+++ b/app/components/dashboard/GlobalSnackbar.tsx
@@ -0,0 +1,24 @@
+import React, { useContext } from 'react';
+import { Snackbar } from '@material-ui/core';
+import { Alert } from '@material-ui/lab';
+import { GlobalSnackbarContext } from '../../context/GlobalSnackbarContext';
+
+export default function GlobalSnackbar() {
+  const globalSnackbarContext = useContext(GlobalSnackbarContext);
+
+  return (
+    <Snackbar
+      anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
+      autoHideDuration={5000}
+      open={globalSnackbarContext?.popupOpen}
+      onClose={() => globalSnackbarContext?.setPopupOpen(false)}
+    >
+      <Alert
+        severity={globalSnackbarContext?.popupSeverity}
+        onClose={() => globalSnackbarContext?.setPopupOpen(false)}
+      >
+        {globalSnackbarContext?.popupMessage}
+      </Alert>
+    </Snackbar>
+  );
+}
diff --git a/app/components/dashboard/IDInfo.tsx b/app/components/dashboard/IDInfo.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..818a9eeaad7c76d193ec87ec4d5934f8e7188fd7
--- /dev/null
+++ b/app/components/dashboard/IDInfo.tsx
@@ -0,0 +1,61 @@
+import React, { useContext } from 'react';
+import { makeStyles, Paper, Typography, Tooltip } from '@material-ui/core';
+import { TabContext } from '../../context/TabContext';
+
+const useStyles = makeStyles((theme) => ({
+  wrapper: {
+    padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
+  },
+  line: {
+    display: 'flex',
+    flexFlow: 'row nowrap',
+    justifyContent: 'flex-start',
+    alignItems: 'center',
+  },
+  itemTitle: {
+    marginRight: theme.spacing(1),
+  },
+}));
+
+export default function IDInfo() {
+  const { state: tabState } = useContext(TabContext);
+  const { activeTabID, manager } = tabState;
+  const connection = manager.connections[activeTabID];
+
+  const classes = useStyles();
+
+  return (
+    <Paper elevation={1} className={classes.wrapper}>
+      <Tooltip title="Runner is responsible for running queries which will be recorded">
+        <div className={classes.line}>
+          <Typography className={classes.itemTitle} variant="subtitle1">
+            Runner
+          </Typography>
+          <Typography variant="subtitle2">
+            {`ConnectionID: ${connection?.runner?.connectionID} ThreadID: ${connection?.runner?.threadID}`}
+          </Typography>
+        </div>
+      </Tooltip>
+      <Tooltip title="Monitor is responsible for monitoring the resource usage of another session">
+        <div className={classes.line}>
+          <Typography className={classes.itemTitle} variant="subtitle1">
+            Monitor
+          </Typography>
+          <Typography variant="subtitle2">
+            {`ConnectionID: ${connection?.monitor?.connectionID}, ThreadID: ${connection?.monitor?.threadID}`}
+          </Typography>
+        </div>
+      </Tooltip>
+      <Tooltip title="Agent is responsible for performing actions when Runner and Monitor are busy">
+        <div className={classes.line}>
+          <Typography className={classes.itemTitle} variant="subtitle1">
+            Agent
+          </Typography>
+          <Typography variant="subtitle2">
+            {`ConnectionID: ${connection?.agent?.connectionID} ThreadID: ${connection?.agent?.threadID}`}
+          </Typography>
+        </div>
+      </Tooltip>
+    </Paper>
+  );
+}
diff --git a/app/components/QueryRecorder.tsx b/app/components/dashboard/QueryRecorder.tsx
similarity index 50%
rename from app/components/QueryRecorder.tsx
rename to app/components/dashboard/QueryRecorder.tsx
index dcd30ecf762c3c4e5150cded5323955e8278b399..84eadac122f2bd0e9a9dfb23ad77179b80f9bb07 100644
--- a/app/components/QueryRecorder.tsx
+++ b/app/components/dashboard/QueryRecorder.tsx
@@ -1,4 +1,5 @@
-import React, { useState, useContext } from 'react';
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import React, { useContext } from 'react';
 import { makeStyles } from '@material-ui/core/styles';
 import {
   Paper,
@@ -9,18 +10,14 @@ import {
   Checkbox,
   CircularProgress,
   Slider,
-  IconButton,
-  Icon,
-  Snackbar,
 } from '@material-ui/core';
 import Switch from '@material-ui/core/Switch';
 import Collapse from '@material-ui/core/Collapse';
 import clsx from 'clsx';
-import { v4 as uuid } from 'uuid';
-import { RecordingContext } from '../context/RecordingContext';
-import SqlManagerSingleton from '../../backend/recorder/SqlManagerSingleton';
-import { RawRecording } from '../../backend/recorder/SqlManager';
+import { RawRecording } from '../../../backend/recorder/SqlManager';
 import ColorPicker from './ColorPicker';
+import { TabContext } from '../../context/TabContext';
+import { RecordingUpdate } from '../../types/RecordingUpdate';
 
 const useStyles = makeStyles((theme) => ({
   container: {
@@ -58,127 +55,137 @@ const useStyles = makeStyles((theme) => ({
 
 const defaultTimestep = 200; // ms
 
-export default function QueryRecorder() {
-  const [query, setQuery] = useState('');
-  const [explainAnalyze, setExplainAnalyze] = useState(false);
-  const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
-  const [timeStep, setTimeStepValue] = useState(
-    (defaultTimestep as number) / 1000
-  );
-  const [recordingLabel, setRecordingLabel] = useState('');
-  const [recordingColor, setRecordingColor] = useState('');
-  const [waitRecording, setWaitRecording] = useState(false);
-  const [userQueryIsRunning, setUserQueryIsRunning] = useState(false);
-  const [managerState, setManagerState] = useState('');
-  const [donePopupOpen, setDonePopupOpen] = useState(false);
-  const [recordingTime, setRecordingTime] = useState(0);
+interface QuerRecorderProps {
+  onNewRecording: (recording: RecordingUpdate) => void;
+}
+
+export default function QueryRecorder(props: QuerRecorderProps) {
+  const { onNewRecording } = props;
 
-  const manager = SqlManagerSingleton.getInstance();
-  const recordingContext = useContext(RecordingContext);
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID, tabs, manager } = tabState;
+  const tab = tabs[activeTabID];
 
-  function updateContext(
-    result: RawRecording,
-    elapsed: number,
-    label: string,
-    color: string,
-    error?: string
-  ) {
-    const uniqueID = uuid();
-    recordingContext?.recordingListItems?.forEach((listItem) => {
-      listItem.viewing = false;
+  function changeRecordingState(state: string) {
+    dispatch({
+      type: 'SET_RECORDING_STATE',
+      payload: { tabID: activeTabID, value: state },
     });
-    const existingListItems = recordingContext?.recordingListItems || [];
-    const existingRecordings = recordingContext?.recordings || [];
-    const newRecording = {
-      queryOutput: result.result,
-      error,
-      stageTimes: !error ? result.stageTimes : undefined,
-      chartData: !error ? result.memoryPerformance : undefined,
-      optimizerTrace: !error ? result.optimizerTrace : undefined,
-      chartColors: !error ? result.chartColors : undefined,
-      explainAnalyze:
-        explainAnalyze || query.toLowerCase().includes('explain analyze'),
-      explainAnalyzeTree: !error ? result.explainAnalyze : undefined,
-      label,
-      uuid: uniqueID,
-    };
-    const newListItem = {
-      query,
+  }
+
+  function changeQuery(query: string) {
+    dispatch({
+      type: 'SET_INPUT_QUERY',
+      payload: { tabID: activeTabID, value: query },
+    });
+  }
+
+  function changeShowAdvancedOptions(show: boolean) {
+    dispatch({
+      type: 'SET_SHOW_ADVANCED_OPTIONS',
+      payload: { tabID: activeTabID, value: show },
+    });
+  }
+
+  function changeExplainAnalyze(explainAnalyze: boolean) {
+    dispatch({
+      type: 'SET_EXPLAIN_ANALYZE',
+      payload: { tabID: activeTabID, value: explainAnalyze },
+    });
+  }
+
+  function changeTimestep(timestep: number) {
+    dispatch({
+      type: 'SET_TIMESTEP',
+      payload: { tabID: activeTabID, value: timestep / 1000 },
+    });
+  }
+
+  function changeRecordingLabel(label: string) {
+    dispatch({
+      type: 'SET_RECORDING_INPUT_LABEL',
+      payload: { tabID: activeTabID, value: label },
+    });
+  }
+
+  function changeRecordingColor(color: string) {
+    dispatch({
+      type: 'SET_RECORDING_INPUT_COLOR',
+      payload: { tabID: activeTabID, value: color },
+    });
+  }
+
+  function changeQueryRunning(isRunning: boolean) {
+    dispatch({
+      type: 'SET_QUERY_RUNNING',
+      payload: { tabID: activeTabID, value: isRunning },
+    });
+  }
+
+  function changeRecordingRunning(isRunning: boolean) {
+    dispatch({
+      type: 'SET_RECORDING_RUNNING',
+      payload: { tabID: activeTabID, value: isRunning },
+    });
+  }
+
+  function updateContext(result?: RawRecording, elapsed?: number, error?: any) {
+    if (error && String(error).includes('cancelled')) {
+      return;
+    }
+    const update = {
+      result,
       elapsed,
-      label,
-      color,
-      uuid: uniqueID,
-      viewing: true,
+      label: tab.inputLabel,
+      color: tab.inputColor,
+      error,
+      explainAnalyze: tab.explainAnalyze,
+      query: tab.inputQuery,
+      tabID: result?.tabID,
     };
-    recordingContext?.setActiveRecording(newRecording);
-    recordingContext?.setRecordingListItems([
-      newListItem,
-      ...existingListItems,
-    ]);
-    recordingContext?.setRecordings([newRecording, ...existingRecordings]);
-    setDonePopupOpen(true);
+    onNewRecording(update);
   }
 
   async function record() {
-    setWaitRecording(true);
-    if (!manager.client) {
+    changeRecordingRunning(true);
+    if (!manager.connections[activeTabID].client) {
       console.error('QueryRecorder: Not connected.');
-      setWaitRecording(false);
+      changeRecordingRunning(false);
       return;
     }
     try {
       // TODO: Get time elapsed from result
       const t0 = performance.now();
       const result = await manager.record(
-        query,
-        setUserQueryIsRunning,
-        timeStep,
-        explainAnalyze,
-        setManagerState
+        tab.inputQuery,
+        activeTabID,
+        changeQueryRunning,
+        changeRecordingState,
+        tab.timestep,
+        tab.explainAnalyze
       );
       const t1 = performance.now();
-      setRecordingTime(t1 - t0);
       if (result?.error) {
-        let error;
-        if (result.error?.info) {
-          const errorcode = JSON.stringify(
-            result?.error?.info?.code,
-            undefined,
-            2
-          );
-          const errormsg = JSON.stringify(
-            result?.error?.info?.msg,
-            undefined,
-            2
-          );
-          error = `Errorcode: ${errorcode}\n${errormsg}`;
-        } else if (result.error === 'cancelled') {
-          error = 'Query was cancelled';
-        } else {
-          error = String(result.error);
-        }
-        updateContext(result, t1 - t0, recordingLabel, recordingColor, error);
+        updateContext(result, t1 - t0, result?.error);
       } else {
-        updateContext(result, t1 - t0, recordingLabel, recordingColor);
+        updateContext(result, t1 - t0);
       }
-      setRecordingLabel('');
     } catch (error) {
       console.error(`QueryRecorder: Error: ${error}`);
-    } finally {
-      setWaitRecording(false);
+      updateContext(undefined, undefined, error);
     }
   }
 
   const classes = useStyles();
 
   function checkShortcut(e: { key: string; ctrlKey: boolean }) {
-    if (e.key === 'Enter' && e.ctrlKey) {
+    if (e.key === 'Enter' && e.ctrlKey && !tab.recordingRunning) {
       record();
     }
   }
 
   function handleSliderChange(_: unknown, newTimeStep: number | number[]) {
-    setTimeStepValue((newTimeStep as number) / 1000);
+    changeTimestep(newTimeStep as number);
   }
 
   function getSliderValueText(value: number) {
@@ -197,7 +204,13 @@ export default function QueryRecorder() {
   ];
 
   function toggleAdvancedOptions() {
-    setShowAdvancedOptions((prev) => !prev);
+    changeShowAdvancedOptions(!tab.showAdvancedOptions);
+  }
+
+  async function cancelRecording() {
+    await manager.cancelRecording(activeTabID);
+    changeQueryRunning(false);
+    changeRecordingRunning(false);
   }
 
   return (
@@ -219,8 +232,8 @@ export default function QueryRecorder() {
             label="Query"
             rows={2}
             rowsMax={10}
-            value={query}
-            onChange={(event) => setQuery(event.target.value)}
+            value={tab.inputQuery}
+            onChange={(event) => changeQuery(event.target.value)}
             onKeyUp={checkShortcut}
             helperText="Ctrl + Enter: Run"
           />
@@ -230,8 +243,8 @@ export default function QueryRecorder() {
             control={
               <Checkbox
                 color="primary"
-                checked={explainAnalyze}
-                onChange={(event) => setExplainAnalyze(event.target.checked)}
+                checked={tab.explainAnalyze}
+                onChange={(event) => changeExplainAnalyze(event.target.checked)}
               />
             }
             label="Explain analyze"
@@ -241,7 +254,7 @@ export default function QueryRecorder() {
           <FormControlLabel
             control={
               <Switch
-                checked={showAdvancedOptions}
+                checked={tab.showAdvancedOptions}
                 onChange={toggleAdvancedOptions}
                 onKeyUp={(event) =>
                   event.key === 'Enter' && toggleAdvancedOptions()
@@ -254,7 +267,7 @@ export default function QueryRecorder() {
           />
         </div>
         <div className={classes.element}>
-          <Collapse in={showAdvancedOptions}>
+          <Collapse in={tab.showAdvancedOptions}>
             <div className={classes.element}>
               <div>
                 <Typography variant="subtitle1">
@@ -279,21 +292,21 @@ export default function QueryRecorder() {
               <TextField
                 variant="filled"
                 label="Recording label"
-                value={recordingLabel}
-                onChange={(event) => setRecordingLabel(event.target.value)}
+                value={tab.inputLabel}
+                onChange={(event) => changeRecordingLabel(event.target.value)}
                 onKeyUp={checkShortcut}
               />
             </div>
             <ColorPicker
-              currentColor={recordingColor}
-              setColor={setRecordingColor}
+              currentColor={tab.inputColor}
+              setColor={changeRecordingColor}
             />
           </Collapse>
         </div>
 
         <div className={clsx(classes.element, classes.recordButtonWrapper)}>
           <Button
-            disabled={waitRecording}
+            disabled={tab.recordingRunning}
             variant="contained"
             color="primary"
             onClick={() => record()}
@@ -302,15 +315,15 @@ export default function QueryRecorder() {
           </Button>
           <Button
             style={{ margin: '8px' }}
-            disabled={!userQueryIsRunning}
+            disabled={!tab.queryRunning}
             variant="outlined"
             color="primary"
-            onClick={() => manager.cancelQuery()}
+            onClick={() => cancelRecording()}
           >
             Cancel
           </Button>
 
-          {waitRecording && (
+          {tab.recordingRunning && (
             <CircularProgress
               style={{ marginLeft: '16px' }}
               size="24px"
@@ -318,30 +331,11 @@ export default function QueryRecorder() {
             />
           )}
         </div>
-        {managerState.length > 0 && (
+        {tab?.recordingState && tab.recordingState.length > 0 && (
           <div className={classes.element}>
-            <Typography variant="subtitle2">{managerState}</Typography>
+            <Typography variant="subtitle2">{tab.recordingState}</Typography>
           </div>
         )}
-        <Snackbar
-          anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
-          autoHideDuration={6000}
-          open={donePopupOpen}
-          onClose={() => setDonePopupOpen(false)}
-          message={`Recording finished in ${recordingTime.toFixed(1)} ms`}
-          action={
-            <>
-              <IconButton
-                size="small"
-                aria-label="close"
-                color="inherit"
-                onClick={() => setDonePopupOpen(false)}
-              >
-                <Icon>close</Icon>
-              </IconButton>
-            </>
-          }
-        />
       </Paper>
     </div>
   );
diff --git a/app/components/RecordingOptions/ChangeColor.tsx b/app/components/dashboard/RecordingOptions/ChangeColor.tsx
similarity index 69%
rename from app/components/RecordingOptions/ChangeColor.tsx
rename to app/components/dashboard/RecordingOptions/ChangeColor.tsx
index a666fe53a8c619d5da1d29cac47f1c16c65c29f1..db58a31720b9d965283de4496807bdbd5395a0a5 100644
--- a/app/components/RecordingOptions/ChangeColor.tsx
+++ b/app/components/dashboard/RecordingOptions/ChangeColor.tsx
@@ -1,19 +1,19 @@
 import React, { useState, useContext } from 'react';
 import { Dialog, DialogTitle, Button, MenuItem } from '@material-ui/core';
-import { RecordingContext } from '../../context/RecordingContext';
 import ColorPicker from '../ColorPicker';
+import { TabContext } from '../../../context/TabContext';
 
 export default function ChangeColor(props: {
-  recordinguuid: string;
+  recordingID: string;
   handleIconMenuClose: () => void;
 }) {
-  const { recordinguuid, handleIconMenuClose } = props;
+  const { recordingID, handleIconMenuClose } = props;
 
   const [changedColor, setChangedColor] = useState('');
   const [changeColorDialogOpen, setChangeColorDialogOpen] = useState(false);
 
-  const recordingContext = useContext(RecordingContext);
-  const recordingListItems = recordingContext?.recordingListItems || [];
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID } = tabState;
 
   function handleChangeColorDialogClose() {
     setChangeColorDialogOpen(false);
@@ -25,13 +25,10 @@ export default function ChangeColor(props: {
   }
 
   function handleChangeColorButtonClick() {
-    const newRecordingListItems = [...recordingListItems];
-    newRecordingListItems.forEach((r) => {
-      if (r.uuid === recordinguuid) {
-        r.color = changedColor;
-      }
+    dispatch({
+      type: 'SET_RECORDING_COLOR',
+      payload: { tabID: activeTabID, recordingID, value: changedColor },
     });
-    recordingContext?.setRecordingListItems(newRecordingListItems);
 
     handleChangeColorDialogClose();
   }
diff --git a/app/components/RecordingOptions/ChangeLabel.tsx b/app/components/dashboard/RecordingOptions/ChangeLabel.tsx
similarity index 62%
rename from app/components/RecordingOptions/ChangeLabel.tsx
rename to app/components/dashboard/RecordingOptions/ChangeLabel.tsx
index 612e07c209b549b507667a520530e615f7412957..1486a81ed80bf19202d891486510c50688190bcc 100644
--- a/app/components/RecordingOptions/ChangeLabel.tsx
+++ b/app/components/dashboard/RecordingOptions/ChangeLabel.tsx
@@ -6,19 +6,18 @@ import {
   Button,
   MenuItem,
 } from '@material-ui/core';
-import { RecordingContext } from '../../context/RecordingContext';
+import { TabContext } from '../../../context/TabContext';
 
 export default function ChangeLabel(props: {
-  recordinguuid: string;
+  recordingID: string;
   handleIconMenuClose: () => void;
 }) {
-  const { recordinguuid, handleIconMenuClose } = props;
+  const { recordingID, handleIconMenuClose } = props;
   const [changedLabel, setChangedLabel] = useState('');
   const [changeLabelDialogOpen, setChangeLabelDialogOpen] = useState(false);
 
-  const recordingContext = useContext(RecordingContext);
-  const recordingListItems = recordingContext?.recordingListItems || [];
-  const recordings = recordingContext?.recordings || [];
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID } = tabState;
 
   function handleChangeLabelDialogClose() {
     setChangeLabelDialogOpen(false);
@@ -30,25 +29,20 @@ export default function ChangeLabel(props: {
   }
 
   function handleChangeLabelButtonClick() {
-    const newRecordingListItems = [...recordingListItems];
-    newRecordingListItems.forEach((r) => {
-      if (r.uuid === recordinguuid) {
-        r.label = changedLabel;
-      }
+    dispatch({
+      type: 'SET_RECORDING_LABEL',
+      payload: { tabID: activeTabID, recordingID, value: changedLabel },
     });
-    recordingContext?.setRecordingListItems(newRecordingListItems);
-
-    const newRecordings = [...recordings];
-    newRecordings.forEach((r) => {
-      if (r.uuid === recordinguuid) {
-        r.label = changedLabel;
-      }
-    });
-    recordingContext?.setRecordings(newRecordings);
 
     handleChangeLabelDialogClose();
   }
 
+  function checkShortcut(key: string) {
+    if (key === 'Enter') {
+      handleChangeLabelButtonClick();
+    }
+  }
+
   return (
     <div>
       <MenuItem onClick={handleChangeLabelOptionClick}>Change label</MenuItem>
@@ -63,6 +57,7 @@ export default function ChangeLabel(props: {
           label="New label"
           value={changedLabel}
           onChange={(event) => setChangedLabel(event.target.value)}
+          onKeyDown={(event) => checkShortcut(event.key)}
         />
         <Button
           variant="contained"
diff --git a/app/components/RecordingOptions/RecordingOptions.tsx b/app/components/dashboard/RecordingOptions/RecordingOptions.tsx
similarity index 52%
rename from app/components/RecordingOptions/RecordingOptions.tsx
rename to app/components/dashboard/RecordingOptions/RecordingOptions.tsx
index 785c3e351ff144fb18401fc65465074eaa580889..7b5bfea962e7b86cb18200dfe65fa76b1908ad51 100644
--- a/app/components/RecordingOptions/RecordingOptions.tsx
+++ b/app/components/dashboard/RecordingOptions/RecordingOptions.tsx
@@ -6,18 +6,27 @@ import {
   MenuItem,
   ListItemIcon,
 } from '@material-ui/core';
+import { valuesIn } from 'lodash';
 import ChangeLabel from './ChangeLabel';
 import ChangeColor from './ChangeColor';
-import { RecordingContext } from '../../context/RecordingContext';
+import { GlobalSnackbarContext } from '../../../context/GlobalSnackbarContext';
+import { TabContext } from '../../../context/TabContext';
 
-export default function RecordingOptions(props: { recordinguuid: string }) {
+interface RecordingOptionsProps {
+  recordingID: string;
+}
+
+export default function RecordingOptions(props: RecordingOptionsProps) {
   const [iconAnchorEl, setIconAnchorEl] = useState<null | HTMLElement>(null);
 
-  const { recordinguuid } = props;
+  const { recordingID } = props;
 
-  const recordingContext = useContext(RecordingContext);
-  const recordingListItems = recordingContext?.recordingListItems || [];
-  const recordings = recordingContext?.recordings || [];
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID, tabs, manager } = tabState;
+  const tab = tabs[activeTabID];
+  const recordingListItems = tab?.recordingListItems || {};
+
+  const globalSnackbarContext = useContext(GlobalSnackbarContext);
 
   function handleIconButtonClick(event: React.MouseEvent<HTMLButtonElement>) {
     setIconAnchorEl(event.currentTarget);
@@ -28,35 +37,20 @@ export default function RecordingOptions(props: { recordinguuid: string }) {
   }
 
   function handleCopyQuery() {
-    const recordingListItem = recordingListItems.find(
-      (r) => r.uuid === recordinguuid
-    );
+    const recordingListItem = recordingListItems[recordingID];
     navigator.clipboard.writeText(recordingListItem?.query || '');
     handleIconMenuClose();
+    globalSnackbarContext?.setPopupMessage('Query copied to clipboard!');
+    globalSnackbarContext?.setPopupSeverity('info');
+    globalSnackbarContext?.setPopupOpen(true);
   }
 
   function handleRemoveRecording() {
-    const activeRecordinguuid = recordingContext?.activeRecording?.uuid || '';
-    recordingContext?.setRecordingListItems(
-      recordingListItems.filter((r) => r.uuid !== recordinguuid)
-    );
-    recordingContext?.setRecordings(
-      recordings.filter((r) => r.uuid !== recordinguuid)
-    );
-    if (activeRecordinguuid === recordinguuid) {
-      const newActiveRecording = recordings.find(
-        (r) => r.uuid !== recordinguuid
-      );
-      recordingContext?.setActiveRecording(newActiveRecording);
-      if (newActiveRecording) {
-        const newActiveRecordingListItem = recordingListItems.find(
-          (r) => r.uuid === newActiveRecording.uuid
-        );
-        if (newActiveRecordingListItem) {
-          newActiveRecordingListItem.viewing = true;
-        }
-      }
-    }
+    dispatch({
+      type: 'DELETE_RECORDING',
+      payload: { tabID: activeTabID, recordingID },
+    });
+
     handleIconMenuClose();
   }
 
@@ -78,11 +72,11 @@ export default function RecordingOptions(props: { recordinguuid: string }) {
         onClose={handleIconMenuClose}
       >
         <ChangeLabel
-          recordinguuid={recordinguuid}
+          recordingID={recordingID}
           handleIconMenuClose={handleIconMenuClose}
         />
         <ChangeColor
-          recordinguuid={recordinguuid}
+          recordingID={recordingID}
           handleIconMenuClose={handleIconMenuClose}
         />
         <MenuItem onClick={handleCopyQuery}>Copy query</MenuItem>
diff --git a/app/components/Recordings.tsx b/app/components/dashboard/Recordings.tsx
similarity index 52%
rename from app/components/Recordings.tsx
rename to app/components/dashboard/Recordings.tsx
index 7efb8e002c06b1d1ee2620ef5488b26bd97aebb5..fac486316c7d4269806b20ff8193e0faf42f355d 100644
--- a/app/components/Recordings.tsx
+++ b/app/components/dashboard/Recordings.tsx
@@ -8,13 +8,18 @@ import {
   Tooltip,
   Typography,
 } from '@material-ui/core';
+import React, { CSSProperties, useContext, useRef, useState } from 'react';
 import InputAdornment from '@material-ui/core/InputAdornment';
 import SearchIcon from '@material-ui/icons/Search';
-import React, { CSSProperties, useContext, useState } from 'react';
+import { keys, valuesIn } from 'lodash';
 import { FixedSizeList } from 'react-window';
 import useDeepCompareEffect from 'use-deep-compare-effect';
-import { RecordingContext } from '../context/RecordingContext';
+import { TabContext } from '../../context/TabContext';
+import { RecordingListItem } from '../../types/RecordingListItem';
 import RecordingOptions from './RecordingOptions/RecordingOptions';
+import SaveRecording from './SaveRecording';
+import { GlobalSnackbarContext } from '../../context/GlobalSnackbarContext';
+import FileIO from '../../../backend/utils/FileIO';
 
 const useStyles = makeStyles((theme) => ({
   wrapper: {
@@ -29,7 +34,6 @@ const useStyles = makeStyles((theme) => ({
     textOverflow: 'ellipsis',
     whiteSpace: 'nowrap',
     overflow: 'hidden',
-    // textAlign: 'left',
   },
   listItemTextRoot: {
     margin: 0,
@@ -38,57 +42,102 @@ const useStyles = makeStyles((theme) => ({
   listItemRoot: {
     padding: 0,
   },
+  actionBar: {
+    display: 'flex',
+    flexFlow: 'row nowrap',
+    justifyContent: 'space-between',
+  },
 }));
 
 export default function Recordings() {
-  const recordingContext = useContext(RecordingContext);
-  const recordings = recordingContext?.recordingListItems || [];
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const recordings = { ...tab?.recordingListItems };
 
-  const [search, setSearch] = useState('');
   const [displayedRecordings, setDisplayedRecordings] = useState(recordings);
 
-  const [activeRecording, setActiveRecording] = useState<string | undefined>(
-    recordings.find((recording) => recording.viewing)?.uuid
-  );
+  const [activeRecordingID, setActiveRecordingID] = useState<
+    string | undefined
+  >(tab?.activeRecordingID);
 
   useDeepCompareEffect(() => {
     // Using useDeepCompareEffect because we want this to happen when 'recordings'
     // changes, even if it is an array (useEffect only supports primitive values)
-    const newActiveRecording = recordings.find((recording) => recording.viewing)
-      ?.uuid;
-    setActiveRecording(newActiveRecording);
+    const newActiveRecordingID = tab?.activeRecordingID;
+    setActiveRecordingID(newActiveRecordingID);
     setDisplayedRecordings(recordings);
+    console.log('Ran recordings update');
   }, [recordings]);
 
   const classes = useStyles();
 
-  function handleRecordingClick(index: number) {
-    if (index < displayedRecordings.length) {
-      const { uuid } = displayedRecordings[index];
-      const originalIndex = recordings.findIndex(
-        (value) => value.uuid === uuid
-      );
-      const newRecordingListItems = recordings;
-      if (activeRecording) {
-        const activeItemIndex = newRecordingListItems.findIndex(
-          (value) => value.uuid === activeRecording
-        );
-        newRecordingListItems[activeItemIndex].viewing = false;
-      }
-      newRecordingListItems[originalIndex].viewing = true;
-      recordingContext?.setRecordingListItems(newRecordingListItems);
-      setActiveRecording(uuid);
-      if (recordingContext?.recordings) {
-        recordingContext?.setActiveRecording(
-          recordingContext.recordings[originalIndex]
-        );
+  function handleRecordingClick(id: string) {
+    if (displayedRecordings[id]) {
+      let newActiveRecordingID = activeRecordingID;
+      setActiveRecordingID(id);
+      if (tab?.recordings) {
+        newActiveRecordingID = id;
       }
+      dispatch({
+        type: 'SET_ACTIVE_RECORDING',
+        payload: {
+          tabID: activeTabID,
+          recordingID: newActiveRecordingID || '',
+        },
+      });
     }
   }
 
-  function handleSaveClick(index: number) {
-    // TODO: Implement
-    console.log(`Clicked ${index} button`);
+  const globalSnackbarContext = useContext(GlobalSnackbarContext);
+
+  const inputRef = useRef<HTMLInputElement>(null);
+
+  function openFileSelector() {
+    inputRef.current?.click();
+  }
+
+  function loadRecording() {
+    if (inputRef.current?.files) {
+      const file = inputRef.current.files[0];
+      if (file) {
+        FileIO.loadRecording(file.path)
+          .then((response) => {
+            if (response) {
+              const tempChartData = response.chartData || [];
+              const chartDataLength = tempChartData?.length || 0;
+              const totalTime =
+                tempChartData[chartDataLength - 1]?.relative_time * 1000 || NaN;
+              const newListItem: RecordingListItem = {
+                query: response.query || 'Query lost',
+                elapsed: totalTime || response.elapsed || -1,
+                label: file.name,
+                color: '',
+                uuid: response.uuid,
+                viewing: true,
+                isSaved: true,
+              };
+              response.label = file.name;
+              dispatch({
+                type: 'ADD_RECORDING',
+                payload: {
+                  tabID: tabState.activeTabID,
+                  recording: response,
+                  recordingListItem: newListItem,
+                  recordingID: response.uuid,
+                },
+              });
+            }
+            return true;
+          })
+          .catch((error) => {
+            console.error(error);
+            globalSnackbarContext?.setPopupMessage(String(error));
+            globalSnackbarContext?.setPopupSeverity('error');
+            globalSnackbarContext?.setPopupOpen(true);
+          });
+      }
+    }
   }
 
   function formatElapsed(elapsed: number) {
@@ -105,22 +154,24 @@ export default function Recordings() {
     return `${elapsed.toFixed(1)} ms`;
   }
 
-  function filterRecordings() {
-    const filtered = recordings.filter((recording) =>
-      recording.label.toLowerCase().includes(search.toLowerCase())
-    );
+  function filterRecordings(query: string) {
+    let filtered: { [key: string]: RecordingListItem } = {};
+    keys(recordings).forEach((id) => {
+      if (recordings[id].label.toLowerCase().includes(query.toLowerCase())) {
+        filtered[id] = recordings[id];
+      }
+    });
+    if (query === '') filtered = recordings;
     setDisplayedRecordings(filtered);
   }
 
-  function checkShortcut(e: { key: string }) {
-    if (e.key === 'Enter') {
-      filterRecordings();
-    }
-  }
+  function renderRow(propss: { index: number; style: CSSProperties }) {
+    const { index, style } = propss;
+    const recording = valuesIn(displayedRecordings)[index];
 
-  function renderRow(props: { index: number; style: CSSProperties }) {
-    const { index, style } = props;
-    const recording = displayedRecordings[index];
+    if (!recording) {
+      return null;
+    }
 
     return (
       <ListItem
@@ -128,7 +179,7 @@ export default function Recordings() {
         style={style}
         key={index}
         button
-        selected={recording.uuid === activeRecording}
+        selected={recording.uuid === activeRecordingID}
       >
         <Tooltip title={index + 1}>
           <ListItemText
@@ -151,7 +202,7 @@ export default function Recordings() {
               flexFlow: 'column nowrap',
               justifyContent: 'center',
             }}
-            onClick={() => handleRecordingClick(index)}
+            onClick={() => handleRecordingClick(recording.uuid)}
           />
         </Tooltip>
         <Tooltip title={recording.label}>
@@ -167,7 +218,7 @@ export default function Recordings() {
               flexBasis: 226,
               paddingRight: 4,
             }}
-            onClick={() => handleRecordingClick(index)}
+            onClick={() => handleRecordingClick(recording.uuid)}
           />
         </Tooltip>
         <Tooltip title={formatElapsed(recording.elapsed)}>
@@ -183,7 +234,7 @@ export default function Recordings() {
               flexBasis: 70,
               paddingRight: 4,
             }}
-            onClick={() => handleRecordingClick(index)}
+            onClick={() => handleRecordingClick(recording.uuid)}
           />
         </Tooltip>
         <Tooltip title={recording.query}>
@@ -194,17 +245,14 @@ export default function Recordings() {
               root: classes.listItemTextRoot,
             }}
             style={{ flexGrow: 1, flexShrink: 1, flexBasis: 150 }}
-            onClick={() => handleRecordingClick(index)}
+            onClick={() => handleRecordingClick(recording.uuid)}
           />
         </Tooltip>
-        <Button
-          variant="outlined"
-          color="primary"
-          onClick={() => handleSaveClick(index)}
-        >
-          Save
-        </Button>
-        <RecordingOptions recordinguuid={recording.uuid} />
+        <SaveRecording
+          recordinguuid={recording.uuid}
+          isSaved={recording.isSaved}
+        />
+        <RecordingOptions recordingID={recording.uuid} />
       </ListItem>
     );
   }
@@ -213,26 +261,49 @@ export default function Recordings() {
     <div className={classes.wrapper}>
       <Paper className={classes.content} elevation={1}>
         <Typography variant="h5">Recordings</Typography>
-        <TextField
-          id="outlined-search"
-          label="Search label..."
-          type="search"
-          variant="outlined"
-          value={search}
-          onChange={(event) => setSearch(event.target.value)}
-          onKeyUp={checkShortcut}
-          InputProps={{
-            endAdornment: (
-              <InputAdornment position="start">
-                <SearchIcon />
-              </InputAdornment>
-            ),
-          }}
-          style={{
-            marginTop: 8,
-            width: 170,
-          }}
-        />
+        <div className={classes.actionBar}>
+          <TextField
+            id="outlined-search"
+            label="Search label..."
+            type="search"
+            variant="outlined"
+            onChange={(event) => filterRecordings(event.target.value)}
+            onKeyPress={(event) => {
+              if (event.key === 'Enter') event.preventDefault();
+            }}
+            InputProps={{
+              endAdornment: (
+                <InputAdornment position="start">
+                  <SearchIcon />
+                </InputAdornment>
+              ),
+            }}
+            style={{
+              marginTop: 8,
+              width: 170,
+            }}
+          />
+          <div>
+            <input
+              type="file"
+              ref={inputRef}
+              style={{ display: 'none' }}
+              accept=".json"
+              onChange={() => loadRecording()}
+            />
+            <Button
+              variant="outlined"
+              color="primary"
+              onClick={() => openFileSelector()}
+              style={{
+                marginTop: 8,
+                height: 40,
+              }}
+            >
+              Load Recording
+            </Button>
+          </div>
+        </div>
         <ListItem classes={{ root: classes.listItemRoot }}>
           <ListItemText
             primary="#"
@@ -298,7 +369,7 @@ export default function Recordings() {
           height={400}
           width="100%"
           itemSize={48}
-          itemCount={displayedRecordings.length}
+          itemCount={valuesIn(displayedRecordings).length}
         >
           {renderRow}
         </FixedSizeList>
diff --git a/app/components/dashboard/SaveRecording.tsx b/app/components/dashboard/SaveRecording.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a1a4efc5896cb707cf3103338cabbfa4c3cfe99d
--- /dev/null
+++ b/app/components/dashboard/SaveRecording.tsx
@@ -0,0 +1,75 @@
+import React, { useState, useContext } from 'react';
+import { Button, Dialog, DialogTitle, TextField } from '@material-ui/core';
+import FileIO from '../../../backend/utils/FileIO';
+import { GlobalSnackbarContext } from '../../context/GlobalSnackbarContext';
+import { TabContext } from '../../context/TabContext';
+
+export default function SaveRecording(props: {
+  recordinguuid: string;
+  isSaved: boolean;
+}) {
+  const { recordinguuid, isSaved } = props;
+
+  const [btnDisabled, setBtnDisabled] = useState(isSaved);
+  const [filename, setFilename] = useState('');
+  const [openFileDialog, setOpenFileDialog] = useState(false);
+
+  const { state: tabState } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const { recordings, recordingListItems } = tab;
+  const recording = recordings[recordinguuid || ''];
+  const recordingListItem = recordingListItems[recordinguuid || ''];
+
+  const globalSnackbarContext = useContext(GlobalSnackbarContext);
+
+  async function handleSaveFile() {
+    if (filename && recording && recordingListItem) {
+      recording.isSaved = true;
+      recordingListItem.isSaved = true;
+      const path = await FileIO.saveRecordingDetails(recording, filename);
+      setOpenFileDialog(false);
+      setBtnDisabled(true);
+      globalSnackbarContext?.setPopupMessage(`Recording saved to ${path}`);
+      globalSnackbarContext?.setPopupSeverity('success');
+      globalSnackbarContext?.setPopupOpen(true);
+    }
+  }
+
+  function handleKeyUp(e: { key: string }) {
+    if (e.key === 'Enter') {
+      handleSaveFile();
+    }
+  }
+
+  return (
+    <div>
+      <Button
+        variant="outlined"
+        color="primary"
+        disabled={btnDisabled}
+        onClick={() => setOpenFileDialog(true)}
+      >
+        Save
+      </Button>
+      <Dialog
+        open={openFileDialog}
+        onClose={() => setOpenFileDialog(false)}
+        aria-labelledby="form-dialog-title"
+      >
+        <DialogTitle id="form-dialog-title">Enter Filename</DialogTitle>
+        <TextField
+          autoFocus
+          variant="filled"
+          label="Filename"
+          value={filename}
+          onKeyUp={handleKeyUp}
+          onChange={(event) => setFilename(event.target.value)}
+        />
+        <Button onClick={handleSaveFile} variant="contained" color="primary">
+          Save
+        </Button>
+      </Dialog>
+    </div>
+  );
+}
diff --git a/app/components/Login.tsx b/app/components/login/Login.tsx
similarity index 83%
rename from app/components/Login.tsx
rename to app/components/login/Login.tsx
index dbf33ab080f4ff5d6445a694cd9f9353843d40ab..42630725b6dc960af902065f658e7f8a7670211f 100644
--- a/app/components/Login.tsx
+++ b/app/components/login/Login.tsx
@@ -11,9 +11,10 @@ import {
 } from '@material-ui/core';
 import { useHistory, useLocation } from 'react-router';
 import clsx from 'clsx';
-import SqlManagerSingleton from '../../backend/recorder/SqlManagerSingleton';
-import routes from '../routes';
-import { LoginDetailsContext } from '../context/LoginDetailsContext';
+import routes from '../../routes';
+import { LoginDetailsContext } from '../../context/LoginDetailsContext';
+import { GlobalSnackbarContext } from '../../context/GlobalSnackbarContext';
+import { TabContext } from '../../context/TabContext';
 
 const useStyles = makeStyles((theme) => ({
   wrapper: {
@@ -61,6 +62,9 @@ const DefaultProps = {
 
 export default function Login({ onConnect = undefined }: LoginProps) {
   const loginDetailsContext = useContext(LoginDetailsContext);
+  const snackBarContext = useContext(GlobalSnackbarContext);
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID, displayTabs, manager, tabs } = tabState;
 
   const history = useHistory();
   const location = useLocation();
@@ -88,20 +92,36 @@ export default function Login({ onConnect = undefined }: LoginProps) {
 
   async function handleConnectByLogin() {
     setConnecting(true);
-    const manager = SqlManagerSingleton.getInstance();
+    const tab = tabs[activeTabID];
     try {
-      await manager.connect(
-        { host, user, password, port },
+      await manager.connect(activeTabID, tab?.serial, {
+        host,
+        user,
+        password,
+        port,
         database,
-        savePassword
-      );
+        savePassword,
+      });
       loginDetailsContext?.setLoginDetails({
-        ...{ host, user, password: savePassword ? password : '', port },
+        ...{ host, user, password, port },
         database,
         savePassword,
       });
       setErrorMessage('');
-
+      snackBarContext?.setPopupMessage('Connected successfully');
+      snackBarContext?.setPopupSeverity('success');
+      snackBarContext?.setPopupOpen(true);
+      const oldLabel = displayTabs[activeTabID].tabLabel;
+      if (
+        oldLabel === 'New connection' ||
+        oldLabel.includes(':') ||
+        oldLabel === ''
+      ) {
+        dispatch({
+          type: 'SET_DISPLAYTAB_LABEL',
+          payload: { id: activeTabID, label: `${host}:${database}` },
+        });
+      }
       if (location.pathname === routes.login) {
         history.push(routes.dashboard);
       } else if (onConnect) {
@@ -131,7 +151,7 @@ export default function Login({ onConnect = undefined }: LoginProps) {
           Default port for X Protocol servers is 33060`
         );
       } else {
-        setErrorMessage('Failed to connect');
+        setErrorMessage(`Failed to connect: ${error}`);
       }
 
       setConnecting(false);
@@ -144,8 +164,6 @@ export default function Login({ onConnect = undefined }: LoginProps) {
     }
   }
 
-  // TODO: Show result of connection (success/failure)
-
   const classes = useStyles();
 
   return (
diff --git a/app/components/ExplainAnalyzeTable.tsx b/app/components/results/ExplainAnalyzeTable.tsx
similarity index 93%
rename from app/components/ExplainAnalyzeTable.tsx
rename to app/components/results/ExplainAnalyzeTable.tsx
index 07cc3a257e0e9a69ba55173acfc9054424a7d0b0..27efd2f9b6eb4b75813757d103243509d46e0502 100644
--- a/app/components/ExplainAnalyzeTable.tsx
+++ b/app/components/results/ExplainAnalyzeTable.tsx
@@ -40,9 +40,11 @@ export default function ExplainAnalyzeTable(data: { node: any }) {
       );
     }
 
+    rows.push(...node.additionalData);
+
     return (
       <TableContainer component={Paper}>
-        <Table className={classes.table} aria-label="simple table">
+        <Table className={classes.table} size="small" aria-label="dense table">
           <TableBody>
             {rows.map((row) => (
               <TableRow key={row.description}>
diff --git a/app/components/results/FlameGraphExplainAnalyze.tsx b/app/components/results/FlameGraphExplainAnalyze.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6c0c3d101c923a5095d4cf97fc9f472ed567c8ae
--- /dev/null
+++ b/app/components/results/FlameGraphExplainAnalyze.tsx
@@ -0,0 +1,128 @@
+import { makeStyles, Typography } from '@material-ui/core';
+import React, { useContext, useRef, useState } from 'react';
+import { FlameGraph } from 'react-flame-graph';
+import { AutoSizer } from 'react-virtualized';
+import { ExplainAnalyzeNode } from '../../../backend/data-processor/types/ExplainAnalyzeNode';
+import { TabContext } from '../../context/TabContext';
+import ExplainAnalyzeTable from './ExplainAnalyzeTable';
+import useSmartTooltip from '../../smartToolTip';
+
+const emptyNode: ExplainAnalyzeNode = {
+  id: '0',
+  offset: -1,
+  name: 'root',
+  time: '',
+  rows: -1,
+  value: 0,
+  timeFirstRow: 0,
+  timeAllRows: 0,
+  loops: -1,
+  additionalData: [],
+  children: [],
+};
+
+type ToolTipState = {
+  mouseX: number;
+  mouseY: number;
+  text: string;
+};
+
+function getMousePos(relativeContainer: any, mouseEvent: any) {
+  if (relativeContainer !== null) {
+    const rect = relativeContainer.getBoundingClientRect();
+    const mouseX = mouseEvent.clientX - rect.left;
+    const mouseY = mouseEvent.clientY - rect.top;
+    return { mouseX, mouseY };
+  }
+  return { mouseX: 0, mouseY: 0 };
+}
+
+const useStyles = makeStyles((theme) => ({
+  header: {
+    padding: theme.spacing(1),
+  },
+}));
+
+export default function FlameGraphExplainAnalyze() {
+  // Might be a better way than using emptyNode?
+  const [selectedNode, setSelectedNode] = useState(emptyNode);
+  const [hoveredNode, setHoveredNode] = useState(emptyNode);
+  const [tooltipState, setTooltipState] = useState<ToolTipState | null>(null);
+
+  const tooltipRef = useSmartTooltip({
+    mouseX: tooltipState === null ? 0 : tooltipState.mouseX,
+    mouseY: tooltipState === null ? 0 : tooltipState.mouseY,
+  });
+
+  const classes = useStyles();
+  const { state: tabState } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const recordings = { ...tab?.recordings };
+  const activeRecordingID = tab?.activeRecordingID || '';
+  const data = recordings[activeRecordingID]?.explainAnalyzeTree;
+  const containerRef = useRef(null);
+  console.log(data);
+
+  return (
+    <div>
+      <div className={classes.header}>
+        <Typography variant="h6">Explain Analyze Flame Graph</Typography>
+      </div>
+      <div ref={containerRef}>
+        <AutoSizer disableHeight>
+          {({ width }) => (
+            <FlameGraph
+              data={data}
+              height={200}
+              width={width}
+              enableTooltips
+              disableDefaultTooltips
+              onChange={(node: { source: ExplainAnalyzeNode }) => {
+                setSelectedNode(node.source);
+              }}
+              onMouseOver={(_event: never, itemData: ExplainAnalyzeNode) => {
+                setHoveredNode(itemData);
+                setTooltipState({
+                  text:
+                    itemData.name !== 'root'
+                      ? `Actual time: ${itemData.time}`
+                      : 'root',
+                  ...getMousePos(containerRef.current, _event),
+                });
+              }}
+              onMouseMove={(_event: never, itemData: ExplainAnalyzeNode) => {
+                setTooltipState({
+                  text:
+                    itemData.name !== 'root'
+                      ? `Actual time: ${itemData.time}`
+                      : 'root',
+                  ...getMousePos(containerRef.current, _event),
+                });
+              }}
+              onMouseOut={() => {
+                setHoveredNode(selectedNode);
+                setTooltipState(null);
+              }}
+            />
+          )}
+        </AutoSizer>
+        {tooltipState !== null && (
+          <div
+            ref={tooltipRef}
+            style={{
+              position: 'absolute',
+              zIndex: 3,
+              backgroundColor: '#000',
+              color: '#fff',
+              padding: '0.5rem',
+            }}
+          >
+            {tooltipState.text}
+          </div>
+        )}
+      </div>
+      <ExplainAnalyzeTable node={hoveredNode} />
+    </div>
+  );
+}
diff --git a/app/components/MemoryChart.tsx b/app/components/results/MemoryPerformance/MemoryChart.tsx
similarity index 69%
rename from app/components/MemoryChart.tsx
rename to app/components/results/MemoryPerformance/MemoryChart.tsx
index 5ae42c553cda33d08b7d7651c4168c9b22145f36..c9f94cbfbdcd5372d4a2665bbaaa3770de8c614e 100644
--- a/app/components/MemoryChart.tsx
+++ b/app/components/results/MemoryPerformance/MemoryChart.tsx
@@ -12,7 +12,7 @@ import {
   Typography,
 } from '@material-ui/core';
 import _ from 'lodash';
-import React, { useState, useContext, CSSProperties } from 'react';
+import React, { useState, useContext, CSSProperties, useEffect } from 'react';
 import { FixedSizeList } from 'react-window';
 import {
   LineChart,
@@ -25,7 +25,7 @@ import {
   ReferenceLine,
   Label,
 } from 'recharts';
-import { RecordingContext } from '../context/RecordingContext';
+import { TabContext } from '../../../context/TabContext';
 
 const useStyles = makeStyles((theme) => ({
   lineChart: {
@@ -52,41 +52,61 @@ const useStyles = makeStyles((theme) => ({
   },
 }));
 
-function getModalStyle() {
-  return {
-    top: '50%',
-    left: '50%',
-    transform: 'translate(-50%,-50%)',
-  };
-}
+const modalStyle = {
+  top: '50%',
+  left: '50%',
+  transform: 'translate(-50%,-50%)',
+};
 
 export default function MemoryChart() {
   const classes = useStyles();
+  const { state: tabState } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const recordings = tab?.recordings || {};
+  const activeRecordingID = tab?.activeRecordingID || '';
+  const stageTimes = recordings[activeRecordingID]?.stageTimes;
+  const chartData = recordings[activeRecordingID]?.chartData;
+  const eventColors = recordings[activeRecordingID]?.chartColors;
+  const top3List = recordings[activeRecordingID]?.top3Memory;
+
   const [disabled, setDisabled] = useState<Array<string>>([]);
   const [validStages, setValidStages] = useState<Array<string>>([]);
-  // const validStages = [
-  //   'stage/sql/preparing',
-  //   'stage/sql/optimizing',
-  //   'stage/sql/executing',
-  //   'stage/sql/end',
-  // ];
-  const [modalStyle] = React.useState(getModalStyle);
-  const [openEvent, setOpenEvent] = React.useState(false);
-  const [openStage, setOpenStage] = React.useState(false);
-  const [checked, setChecked] = useState<Array<string>>([]);
-  const [filteredMemory, setFilteredMemory] = useState<Array<string>>([]);
+  const [openEvent, setOpenEvent] = useState(false);
+  const [openStage, setOpenStage] = useState(false);
+  const [eventsChecked, setEventsChecked] = useState<Array<string>>([]);
+  const [stagesChecked, setStagesChecked] = useState<Array<string>>([]);
+  const [filteredEvents, setFilteredEvents] = useState<Array<string>>([]);
   const [filteredStages, setFilteredStages] = useState<Array<string>>([]);
+  const [eventCheckAll, setEventCheckAll] = useState(true);
+  const [stageCheckAll, setStageCheckAll] = useState(false);
 
-  const recordingContext = useContext(RecordingContext);
-  const stageTimes = recordingContext?.activeRecording?.stageTimes;
-  const chartData = recordingContext?.activeRecording?.chartData;
-  const testColors = recordingContext?.activeRecording?.chartColors;
+  useEffect(() => {
+    if (top3List && chartData) {
+      const notTop3 = _.filter(
+        _.keys(chartData[0]).slice(1),
+        (o) => !_.includes(top3List, o)
+      );
+      setDisabled(notTop3);
+      setEventsChecked(notTop3);
+    }
+  }, [top3List]);
 
   function getMaxTime() {
     // eslint-disable-next-line prefer-spread
     return Math.max.apply(Math, _.map(chartData, 'relative_time'));
   }
 
+  function formatBytes(byte: number) {
+    if (Math.abs(byte) > 10000000) {
+      return `${Math.floor(byte / 1024 / 1024)} MB`;
+    }
+    if (Math.abs(byte) > 10000) {
+      return `${Math.floor(byte / 1024)} KB`;
+    }
+    return `${Math.floor(byte)} B`;
+  }
+
   // When a legend is clicked the datakey is added to the disabled list, which will remove it form the line chart
   function eventToggle(dataKey: string) {
     if (_.includes(disabled, dataKey)) {
@@ -104,42 +124,68 @@ export default function MemoryChart() {
     }
   }
 
-  function formatBytes(byte: number) {
-    if (Math.abs(byte) > 10000000) {
-      return `${Math.floor(byte / 1024 / 1024)} MB`;
-    }
-    if (Math.abs(byte) > 10000) {
-      return `${Math.floor(byte / 1024)} KB`;
+  function handleEventToggle(value: string) {
+    const curIndex = eventsChecked.indexOf(value);
+    const newChecked = [...eventsChecked];
+
+    if (curIndex === -1) {
+      eventToggle(value);
+      newChecked.push(value);
+    } else {
+      eventToggle(value);
+      newChecked.splice(curIndex, 1);
     }
-    return `${Math.floor(byte)} B`;
+    setEventsChecked(newChecked);
   }
 
-  function handleToggle(value: string, memory: boolean) {
-    const curIndex = checked.indexOf(value);
-    const newChecked = [...checked];
+  function handleStageToggle(value: string) {
+    const curIndex = stagesChecked.indexOf(value);
+    const newChecked = [...stagesChecked];
 
     if (curIndex === -1) {
-      if (memory) {
-        eventToggle(value);
-      } else {
-        validStageToggle(value);
-      }
+      validStageToggle(value);
       newChecked.push(value);
     } else {
-      if (memory) {
-        eventToggle(value);
-      } else {
-        validStageToggle(value);
-      }
+      validStageToggle(value);
       newChecked.splice(curIndex, 1);
     }
-    setChecked(newChecked);
+    setStagesChecked(newChecked);
+  }
+
+  // Turns on/off all events
+  function handleEventCheckAllToggle() {
+    if (eventCheckAll === true) {
+      setEventCheckAll(false);
+      if (chartData) {
+        setEventsChecked(_.keys(chartData[0]).slice(1));
+        setDisabled(_.keys(chartData[0]).slice(1));
+      }
+    } else {
+      setEventCheckAll(true);
+      setEventsChecked([]);
+      setDisabled([]);
+    }
+  }
+
+  // Turns on/off all stages
+  function handleStageCheckAllToggle() {
+    if (stageCheckAll === true) {
+      setStageCheckAll(false);
+      setStagesChecked([]);
+      setValidStages([]);
+    } else {
+      setStageCheckAll(true);
+      if (stageTimes) {
+        setStagesChecked(_.map(_.uniqBy(stageTimes, 'stage'), 'stage'));
+        setValidStages(_.map(_.uniqBy(stageTimes, 'stage'), 'stage'));
+      }
+    }
   }
 
   // Opens the modal to select event
   function handleOpenEvent() {
     if (chartData) {
-      setFilteredMemory(_.keys(chartData[0]).slice(1));
+      setFilteredEvents(_.keys(chartData[0]).slice(1));
     }
     setOpenEvent(true);
   }
@@ -160,7 +206,7 @@ export default function MemoryChart() {
     setOpenStage(false);
   }
 
-  // Sets filteredMemory with a list of the datakeys that matches the text in the search bar
+  // Sets filteredEvents with a list of the datakeys that matches the text in the search bar
   function searchBarFilter(search: string, memory: boolean) {
     let filteredList = [''];
     if (memory) {
@@ -169,7 +215,7 @@ export default function MemoryChart() {
           _.includes(o.toLowerCase(), search.toLowerCase())
         );
       }
-      setFilteredMemory(filteredList);
+      setFilteredEvents(filteredList);
     } else {
       if (stageTimes) {
         filteredList = _.filter(
@@ -184,30 +230,30 @@ export default function MemoryChart() {
   function renderEventRow(props: { index: number; style: CSSProperties }) {
     const { index, style } = props;
 
-    const memoryType = filteredMemory[index];
+    const eventType = filteredEvents[index];
 
-    const labelId = `checkbox-list-label-${memoryType}`;
+    const labelId = `checkbox-list-label-${eventType}`;
     return (
       <ListItem
         classes={{ root: classes.listItemRoot }}
         style={style}
-        key={memoryType}
+        key={eventType}
         role={undefined}
         dense
         button
-        onClick={() => handleToggle(memoryType, true)}
+        onClick={() => handleEventToggle(eventType)}
       >
         <ListItemIcon>
           <Checkbox
             edge="start"
-            checked={checked.indexOf(memoryType) === -1}
+            checked={eventsChecked.indexOf(eventType) === -1}
             tabIndex={-1}
             disableRipple
             inputProps={{ 'aria-labelledby': labelId }}
             color="primary"
           />
         </ListItemIcon>
-        <ListItemText id={labelId} primary={`${memoryType}`} />
+        <ListItemText id={labelId} primary={`${eventType}`} />
       </ListItem>
     );
   }
@@ -226,12 +272,12 @@ export default function MemoryChart() {
         role={undefined}
         dense
         button
-        onClick={() => handleToggle(stage, false)}
+        onClick={() => handleStageToggle(stage)}
       >
         <ListItemIcon>
           <Checkbox
             edge="start"
-            checked={checked.indexOf(stage) !== -1}
+            checked={stagesChecked.indexOf(stage) !== -1}
             tabIndex={-1}
             disableRipple
             inputProps={{ 'aria-labelledby': labelId }}
@@ -278,13 +324,25 @@ export default function MemoryChart() {
                   label="Search event..."
                   variant="filled"
                   onChange={(e) => searchBarFilter(e.target.value, true)}
+                  onKeyPress={(e) => {
+                    if (e.key === 'Enter') e.preventDefault();
+                  }}
                 />
               </form>
+              <Checkbox
+                edge="start"
+                checked={eventCheckAll}
+                tabIndex={-1}
+                disableRipple
+                inputProps={{ 'aria-labelledby': 'eventCheckAll' }}
+                color="primary"
+                onClick={() => handleEventCheckAllToggle()}
+              />
               <FixedSizeList
                 height={400}
                 width="100%"
                 itemSize={48}
-                itemCount={filteredMemory.length}
+                itemCount={filteredEvents.length}
               >
                 {renderEventRow}
               </FixedSizeList>
@@ -316,8 +374,20 @@ export default function MemoryChart() {
                   label="Search stage"
                   variant="filled"
                   onChange={(e) => searchBarFilter(e.target.value, false)}
+                  onKeyPress={(e) => {
+                    if (e.key === 'Enter') e.preventDefault();
+                  }}
                 />
               </form>
+              <Checkbox
+                edge="start"
+                checked={stageCheckAll}
+                tabIndex={-1}
+                disableRipple
+                inputProps={{ 'aria-labelledby': 'eventCheckAll' }}
+                color="primary"
+                onClick={() => handleStageCheckAllToggle()}
+              />
               <FixedSizeList
                 height={400}
                 width="100%"
@@ -330,6 +400,7 @@ export default function MemoryChart() {
           </Fade>
         </Modal>
       </div>
+
       {chartData && chartData.length > 2 ? (
         <div>
           <ResponsiveContainer height={400}>
@@ -356,12 +427,14 @@ export default function MemoryChart() {
               />
               <Tooltip
                 formatter={(value) => formatBytes(Number(value))}
+                label="relative_time"
+                labelFormatter={(name) => `Time Taken: ${name}`}
                 offset={150}
               />
 
               {/* Creates an array from the key:value pairs in chartColors.
              The keys that are not disabled becomes a line */}
-              {_.toPairs(testColors)
+              {_.toPairs(eventColors)
                 .filter((pair) => !_.includes(disabled, pair[0]))
                 .map((pair) => (
                   <Line
diff --git a/app/components/results/MemoryPerformance/MemoryEvents.tsx b/app/components/results/MemoryPerformance/MemoryEvents.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..adc5c10c755f1fa6808ae94590f50e3b2ae2431a
--- /dev/null
+++ b/app/components/results/MemoryPerformance/MemoryEvents.tsx
@@ -0,0 +1,227 @@
+import React, { useContext, CSSProperties, useState } from 'react';
+import _ from 'lodash';
+import { FixedSizeList } from 'react-window';
+import {
+  Backdrop,
+  Button,
+  Checkbox,
+  Fade,
+  ListItem,
+  ListItemIcon,
+  ListItemText,
+  makeStyles,
+  Modal,
+  TextField,
+  Typography,
+} from '@material-ui/core';
+import { MemoryPerformanceContext } from '../../../context/MemoryPerformanceContext';
+import { TabContext } from '../../../context/TabContext';
+
+const useStyles = makeStyles((theme) => ({
+  element: {
+    marginBottom: theme.spacing(1),
+  },
+  paper: {
+    position: 'absolute',
+    width: 500,
+    backgroundColor: theme.palette.background.paper,
+    padding: theme.spacing(2, 4, 3),
+    outline: 'none',
+    borderRadius: 4,
+  },
+  listItemRoot: {
+    padding: 0,
+  },
+}));
+
+export default function MemoryEvents() {
+  const classes = useStyles();
+  const memoryPerformanceContext = useContext(MemoryPerformanceContext);
+  const [disabled, setDisabled] = [
+    memoryPerformanceContext?.disabled,
+    memoryPerformanceContext?.setDisabled,
+  ];
+  const [openEvent, setOpenEvent] = [
+    memoryPerformanceContext?.openEvent,
+    memoryPerformanceContext?.setOpenEvent,
+  ];
+  const [eventsChecked, setEventsChecked] = [
+    memoryPerformanceContext?.eventsChecked,
+    memoryPerformanceContext?.setEventsChecked,
+  ];
+  const [filteredEvents, setFilteredEvents] = [
+    memoryPerformanceContext?.filteredEvents,
+    memoryPerformanceContext?.setFilteredEvents,
+  ];
+  const [allChecked, setAllChecked] = useState(false);
+
+  const { state: tabState } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const { recordings, activeRecordingID } = tab;
+  const recording = recordings[activeRecordingID || ''];
+  const chartData = recording?.chartData;
+
+  const modalStyle = {
+    top: '50%',
+    left: '50%',
+    transform: 'translate(-50%,-50%)',
+  };
+
+  // When a legend is clicked the datakey is added to the disabled list, which will remove it form the line chart
+  function eventToggle(dataKey: string) {
+    if (disabled) {
+      if (_.includes(disabled, dataKey)) {
+        setDisabled?.call(
+          undefined,
+          disabled.filter((obj) => obj !== dataKey)
+        );
+      } else {
+        setDisabled?.call(undefined, disabled.concat(dataKey));
+      }
+    }
+  }
+
+  function handleEventToggle(value: string) {
+    const curIndex = eventsChecked?.indexOf(value);
+    const newChecked = [...(eventsChecked || '')];
+
+    if (curIndex === -1) {
+      eventToggle(value);
+      newChecked.push(value);
+    } else {
+      eventToggle(value);
+      if (curIndex !== undefined) {
+        newChecked.splice(curIndex, 1);
+      }
+    }
+    setEventsChecked?.call(undefined, newChecked);
+  }
+
+  function handleEventCheckAllToggle() {
+    if (allChecked) {
+      setAllChecked(false);
+      if (chartData) {
+        setEventsChecked?.call(undefined, _.keys(chartData[0]).slice(1));
+        setDisabled?.call(undefined, _.keys(chartData[0]).slice(1));
+      }
+    } else {
+      setAllChecked(true);
+      setEventsChecked?.call(undefined, []);
+      setDisabled?.call(undefined, []);
+    }
+  }
+
+  // Opens the modal to select event
+  function handleOpenEvent() {
+    if (chartData) {
+      setFilteredEvents?.call(undefined, _.keys(chartData[0]).slice(1));
+    }
+    setOpenEvent?.call(undefined, true);
+  }
+
+  function handleCloseEvent() {
+    setOpenEvent?.call(undefined, false);
+  }
+
+  // Sets filteredEvents with a list of the datakeys that matches the text in the search bar
+  function searchBarFilter(search: string) {
+    let filteredList = [''];
+    if (chartData) {
+      filteredList = _.filter(_.keys(chartData[0]).slice(1), (o) =>
+        _.includes(o.toLowerCase(), search.toLowerCase())
+      );
+    }
+    setFilteredEvents?.call(undefined, filteredList);
+  }
+
+  function renderEventRow(props: { index: number; style: CSSProperties }) {
+    const { index, style } = props;
+
+    const eventType = filteredEvents ? filteredEvents[index] : '';
+
+    const labelId = `checkbox-list-label-${eventType}`;
+    return (
+      <ListItem
+        classes={{ root: classes.listItemRoot }}
+        style={style}
+        key={eventType}
+        role={undefined}
+        dense
+        button
+        onClick={() => handleEventToggle(eventType)}
+      >
+        <ListItemIcon>
+          <Checkbox
+            edge="start"
+            checked={eventsChecked?.indexOf(eventType) === -1}
+            tabIndex={-1}
+            disableRipple
+            inputProps={{ 'aria-labelledby': labelId }}
+            color="primary"
+          />
+        </ListItemIcon>
+        <ListItemText id={labelId} primary={`${eventType}`} />
+      </ListItem>
+    );
+  }
+
+  return (
+    <div>
+      {' '}
+      <Button
+        variant="outlined"
+        color="primary"
+        onClick={handleOpenEvent}
+        style={{ marginRight: 16 }}
+      >
+        Pick Events
+      </Button>
+      <Modal
+        open={openEvent !== undefined ? openEvent : false}
+        onClose={handleCloseEvent}
+        aria-labelledby="simple-modal-title"
+        closeAfterTransition
+        BackdropComponent={Backdrop}
+        BackdropProps={{
+          timeout: 500,
+        }}
+      >
+        <Fade in={openEvent}>
+          <div style={modalStyle} className={classes.paper}>
+            <div className={classes.element}>
+              <Typography variant="h5">Pick events to show</Typography>
+            </div>
+            <form>
+              <TextField
+                id="filled-basic"
+                label="Search event..."
+                variant="filled"
+                onChange={(e) => searchBarFilter(e.target.value)}
+              />
+            </form>
+            <Checkbox
+              edge="start"
+              checked={allChecked}
+              tabIndex={-1}
+              disableRipple
+              inputProps={{ 'aria-labelledby': 'allChecked' }}
+              color="primary"
+              onClick={() => handleEventCheckAllToggle()}
+            />
+            <FixedSizeList
+              height={400}
+              width="100%"
+              itemSize={48}
+              itemCount={
+                filteredEvents !== undefined ? filteredEvents?.length : 0
+              }
+            >
+              {renderEventRow}
+            </FixedSizeList>
+          </div>
+        </Fade>
+      </Modal>
+    </div>
+  );
+}
diff --git a/app/components/results/MemoryPerformance/MemoryPerformance.tsx b/app/components/results/MemoryPerformance/MemoryPerformance.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..470c3b8bb25a56e2c1363c6ca5fc6501a9822bcf
--- /dev/null
+++ b/app/components/results/MemoryPerformance/MemoryPerformance.tsx
@@ -0,0 +1,170 @@
+import { makeStyles, Typography } from '@material-ui/core';
+import _ from 'lodash';
+import React, { useContext, useEffect } from 'react';
+import {
+  LineChart,
+  Line,
+  CartesianGrid,
+  XAxis,
+  YAxis,
+  Tooltip,
+  ResponsiveContainer,
+  ReferenceLine,
+  Label,
+} from 'recharts';
+import MemoryEvents from './MemoryEvents';
+import MemoryStages from './MemoryStages';
+import { MemoryPerformanceContext } from '../../../context/MemoryPerformanceContext';
+import { TabContext } from '../../../context/TabContext';
+
+const useStyles = makeStyles((theme) => ({
+  lineChart: {
+    zIndex: 999,
+  },
+  buttonWrapper: {
+    display: 'flex',
+    flexFlow: 'row nowrap',
+    padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
+  },
+}));
+
+export default function MemoryPerformance() {
+  const classes = useStyles();
+
+  const memoryPerformanceContext = useContext(MemoryPerformanceContext);
+  const { state: tabState } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const { recordings, activeRecordingID } = tab;
+  const recording = recordings[activeRecordingID || ''];
+  const stageTimes = recording?.stageTimes;
+  const chartData = recording?.chartData;
+  const testColors = recording?.chartColors;
+  const top3List = recording?.top3Memory;
+
+  const [eventsChecked, setEventsChecked] = [
+    memoryPerformanceContext?.eventsChecked,
+    memoryPerformanceContext?.setEventsChecked,
+  ];
+  const [disabled, setDisabled] = [
+    memoryPerformanceContext?.disabled,
+    memoryPerformanceContext?.setDisabled,
+  ];
+
+  const validStages = memoryPerformanceContext?.validStages;
+
+  useEffect(() => {
+    if (top3List && chartData) {
+      const notTop3 = _.filter(
+        _.keys(chartData[0]).slice(1),
+        (o) => !_.includes(top3List, o)
+      );
+      setDisabled?.call(undefined, notTop3);
+      setEventsChecked?.call(undefined, notTop3);
+    }
+  }, [top3List]);
+
+  function getMaxTime() {
+    // eslint-disable-next-line prefer-spread
+    return Math.max.apply(Math, _.map(chartData, 'relative_time'));
+  }
+
+  function formatBytes(byte: number) {
+    if (Math.abs(byte) > 10000000) {
+      return `${Math.floor(byte / 1024 / 1024)} MB`;
+    }
+    if (Math.abs(byte) > 10000) {
+      return `${Math.floor(byte / 1024)} KB`;
+    }
+    return `${Math.floor(byte)} B`;
+  }
+
+  return (
+    <div className={classes.lineChart}>
+      <div className={classes.buttonWrapper}>
+        <div style={{ flexGrow: 1 }}>
+          <Typography variant="h6">Memory performance</Typography>
+        </div>
+        <MemoryEvents />
+        <MemoryStages />
+      </div>
+
+      {chartData && chartData.length > 2 ? (
+        <div>
+          <ResponsiveContainer height={400}>
+            <LineChart
+              width={800}
+              height={500}
+              data={chartData}
+              margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
+              style={{ fontFamily: 'Roboto, sans-serif' }}
+            >
+              <CartesianGrid strokeDasharray="3 10" />
+              {/* TODO: Create our own Axis tick component like this: http://recharts.org/en-US/examples/CustomizedLabelLineChart */}
+              <XAxis
+                dataKey="relative_time"
+                type="number"
+                domain={[0, getMaxTime()]}
+                tickCount={20}
+                stroke="#FFFFFF"
+              />
+              <YAxis
+                type="number"
+                tickFormatter={formatBytes}
+                stroke="#FFFFFF"
+              />
+              <Tooltip
+                formatter={(value) => formatBytes(Number(value))}
+                offset={150}
+              />
+
+              {/* Creates an array from the key:value pairs in chartColors.
+             The keys that are not disabled becomes a line */}
+              {_.toPairs(testColors)
+                .filter((pair) => !_.includes(disabled, pair[0]))
+                .map((pair) => (
+                  <Line
+                    type="monotone"
+                    dataKey={pair[0]}
+                    key={pair[0]}
+                    stroke={pair[1]}
+                    dot={false}
+                  />
+                ))}
+              {/* Filters stageTimes to only include stages that are in validStages and only the first instance of them.
+                  en creates reference lines from the data that remains */}
+
+              {_.forEach(_.uniqBy(stageTimes, 'stage'))
+                ?.filter((stage) => _.includes(validStages, stage.stage))
+                ?.map((item) => (
+                  <ReferenceLine
+                    x={item.startTime}
+                    stroke="#769CFF"
+                    key={_.uniqueId('ref_')}
+                  >
+                    <Label
+                      value={item.stage}
+                      position="top"
+                      style={{ color: '#FFFFFF' }}
+                    />
+                  </ReferenceLine>
+                ))}
+            </LineChart>
+          </ResponsiveContainer>
+        </div>
+      ) : (
+        <div>
+          {chartData ? (
+            <Typography variant="subtitle1" style={{ padding: '8px 16px' }}>
+              There are not enough data points to show a meaningful graph
+            </Typography>
+          ) : (
+            <Typography variant="subtitle1" style={{ padding: '8px 16px' }}>
+              No data
+            </Typography>
+          )}
+        </div>
+      )}
+    </div>
+  );
+}
diff --git a/app/components/results/MemoryPerformance/MemoryStages.tsx b/app/components/results/MemoryPerformance/MemoryStages.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4521b6c9b34a9d05b23e60d923039fe8a4f13b46
--- /dev/null
+++ b/app/components/results/MemoryPerformance/MemoryStages.tsx
@@ -0,0 +1,230 @@
+import React, { useContext, CSSProperties, useState } from 'react';
+import _ from 'lodash';
+import { FixedSizeList } from 'react-window';
+import {
+  Backdrop,
+  Button,
+  Checkbox,
+  Fade,
+  ListItem,
+  ListItemIcon,
+  ListItemText,
+  makeStyles,
+  Modal,
+  TextField,
+  Typography,
+} from '@material-ui/core';
+import { MemoryPerformanceContext } from '../../../context/MemoryPerformanceContext';
+import { TabContext } from '../../../context/TabContext';
+
+const useStyles = makeStyles((theme) => ({
+  element: {
+    marginBottom: theme.spacing(1),
+  },
+  paper: {
+    position: 'absolute',
+    width: 500,
+    backgroundColor: theme.palette.background.paper,
+    padding: theme.spacing(2, 4, 3),
+    outline: 'none',
+    borderRadius: 4,
+  },
+  listItemRoot: {
+    padding: 0,
+  },
+}));
+
+export default function MemoryStages() {
+  const classes = useStyles();
+  const memoryPerformanceContext = useContext(MemoryPerformanceContext);
+  const [validStages, setValidStages] = [
+    memoryPerformanceContext?.validStages,
+    memoryPerformanceContext?.setValidStages,
+  ];
+  const [openStage, setOpenStage] = [
+    memoryPerformanceContext?.openStage,
+    memoryPerformanceContext?.setOpenStage,
+  ];
+  const [stagesChecked, setStagesChecked] = [
+    memoryPerformanceContext?.stagesChecked,
+    memoryPerformanceContext?.setStagesChecked,
+  ];
+  const [filteredStages, setFilteredStages] = [
+    memoryPerformanceContext?.filteredStages,
+    memoryPerformanceContext?.setFilteredStages,
+  ];
+  const [allChecked, setAllChecked] = useState(false);
+
+  const { state: tabState } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const { recordings, activeRecordingID } = tab;
+  const recording = recordings[activeRecordingID || ''];
+  const stageTimes = recording?.stageTimes;
+
+  const modalStyle = {
+    top: '50%',
+    left: '50%',
+    transform: 'translate(-50%,-50%)',
+  };
+
+  function validStageToggle(stage: string) {
+    if (_.includes(validStages, stage) && validStages) {
+      setValidStages?.call(
+        undefined,
+        validStages?.filter((obj) => obj !== stage)
+      );
+    } else if (validStages) {
+      setValidStages?.call(undefined, validStages?.concat(stage));
+    }
+  }
+
+  function handleStageToggle(value: string) {
+    const curIndex = stagesChecked?.indexOf(value);
+    const newChecked = [...(stagesChecked || '')];
+
+    if (curIndex === -1) {
+      validStageToggle(value);
+      newChecked.push(value);
+    } else {
+      validStageToggle(value);
+
+      if (curIndex !== undefined) {
+        newChecked.splice(curIndex, 1);
+      }
+    }
+    setStagesChecked?.call(undefined, newChecked);
+  }
+
+  function handleStageCheckAllToggle() {
+    if (allChecked) {
+      setAllChecked(false);
+      setStagesChecked?.call(undefined, []);
+      setValidStages?.call(undefined, []);
+    } else {
+      setAllChecked(true);
+      if (stageTimes) {
+        setStagesChecked?.call(
+          undefined,
+          _.map(_.uniqBy(stageTimes, 'stage'), 'stage')
+        );
+        setValidStages?.call(
+          undefined,
+          _.map(_.uniqBy(stageTimes, 'stage'), 'stage')
+        );
+      }
+    }
+  }
+
+  // Opens the modal to select event
+  function handleOpenStage() {
+    if (stageTimes) {
+      setFilteredStages?.call(
+        undefined,
+        _.map(_.uniqBy(stageTimes, 'stage'), 'stage')
+      );
+    }
+    setOpenStage?.call(undefined, true);
+  }
+
+  function handleCloseStage() {
+    setOpenStage?.call(undefined, false);
+  }
+
+  // Sets filteredEvents with a list of the datakeys that matches the text in the search bar
+  function searchBarFilter(search: string) {
+    let filteredList = [''];
+
+    if (stageTimes) {
+      filteredList = _.filter(
+        _.map(_.uniqBy(stageTimes, 'stage'), 'stage'),
+        (o) => _.includes(o.toLowerCase(), search.toLowerCase())
+      );
+    }
+    setFilteredStages?.call(undefined, filteredList);
+  }
+
+  function renderStageRow(props: { index: number; style: CSSProperties }) {
+    const { index, style } = props;
+
+    const stage = filteredStages ? filteredStages[index] : '';
+
+    const labelId = `checkbox-list-label-${stage}`;
+    return (
+      <ListItem
+        classes={{ root: classes.listItemRoot }}
+        style={style}
+        key={stage}
+        role={undefined}
+        dense
+        button
+        onClick={() => handleStageToggle(stage)}
+      >
+        <ListItemIcon>
+          <Checkbox
+            edge="start"
+            checked={stagesChecked?.indexOf(stage) !== -1}
+            tabIndex={-1}
+            disableRipple
+            inputProps={{ 'aria-labelledby': labelId }}
+            color="primary"
+          />
+        </ListItemIcon>
+        <ListItemText id={labelId} primary={`${stage}`} />
+      </ListItem>
+    );
+  }
+  return (
+    <div>
+      {/* Stage modal */}
+      <Button variant="outlined" color="primary" onClick={handleOpenStage}>
+        Pick Stages
+      </Button>
+      <Modal
+        open={openStage !== undefined ? openStage : false}
+        onClose={handleCloseStage}
+        aria-labelledby="simple-modal-title"
+        closeAfterTransition
+        BackdropComponent={Backdrop}
+        BackdropProps={{
+          timeout: 500,
+        }}
+      >
+        <Fade in={openStage}>
+          <div style={modalStyle} className={classes.paper}>
+            <div className={classes.element}>
+              <Typography variant="h5">Pick stages to show</Typography>
+            </div>
+            <form>
+              <TextField
+                id="filled-basic"
+                label="Search stage"
+                variant="filled"
+                onChange={(e) => searchBarFilter(e.target.value)}
+              />
+            </form>
+            <Checkbox
+              edge="start"
+              checked={allChecked}
+              tabIndex={-1}
+              disableRipple
+              inputProps={{ 'aria-labelledby': 'eventCheckAll' }}
+              color="primary"
+              onClick={() => handleStageCheckAllToggle()}
+            />
+            <FixedSizeList
+              height={400}
+              width="100%"
+              itemSize={48}
+              itemCount={
+                filteredStages !== undefined ? filteredStages?.length : 0
+              }
+            >
+              {renderStageRow}
+            </FixedSizeList>
+          </div>
+        </Fade>
+      </Modal>
+    </div>
+  );
+}
diff --git a/app/components/OptimizerTrace.tsx b/app/components/results/OptimizerTrace.tsx
similarity index 82%
rename from app/components/OptimizerTrace.tsx
rename to app/components/results/OptimizerTrace.tsx
index 529d9c92e69700564767120bbaa740f4cdb5f672..815753ddfc338ba3c4e2b7460b8ab88df73ce9a1 100644
--- a/app/components/OptimizerTrace.tsx
+++ b/app/components/results/OptimizerTrace.tsx
@@ -2,7 +2,7 @@ import { Button, Typography } from '@material-ui/core';
 import React, { useState, useContext } from 'react';
 import ReactJson from 'react-json-view';
 import { makeStyles } from '@material-ui/core/styles';
-import { RecordingContext } from '../context/RecordingContext';
+import { TabContext } from '../../context/TabContext';
 
 export default function OptimizerTrace() {
   const [expandAll, setExpandAll] = useState<boolean | undefined>(undefined);
@@ -28,13 +28,17 @@ export default function OptimizerTrace() {
       position: 'sticky',
       top: 0,
       zIndex: 1,
-      backgroundColor: '#4B4B4B',
+      backgroundColor: theme.palette.background.paper,
     },
     trace: { zIndex: 0 },
   }));
 
-  const recordingContext = useContext(RecordingContext);
-  const optimizerTrace = recordingContext?.activeRecording?.optimizerTrace;
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const recordings = tab?.recordings || {};
+  const activeRecordingID = tab?.activeRecordingID || '';
+  const optimizerTrace = recordings[activeRecordingID]?.optimizerTrace;
 
   function handleButtonClick() {
     if (typeof expandAll === 'undefined') {
diff --git a/app/components/results/ResultGrid.tsx b/app/components/results/ResultGrid.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f17407776dd1ad0a97f35468e72f548559f0a88a
--- /dev/null
+++ b/app/components/results/ResultGrid.tsx
@@ -0,0 +1,153 @@
+/* eslint-disable no-plusplus */
+/* eslint-disable linebreak-style */
+import React, { CSSProperties, useContext, useEffect, useState } from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import { Typography } from '@material-ui/core';
+
+import { AutoSizer, MultiGrid } from 'react-virtualized';
+import { ResultValue } from '@mysql/xdevapi';
+import { TabContext } from '../../context/TabContext';
+
+const useStyles = makeStyles((theme) => ({
+  tableWrapper: {
+    minHeight: 200,
+    height: '100%',
+    width: '100%',
+    resize: 'vertical',
+    overflowY: 'hidden',
+    scrollbarWidth: 'none',
+    paddingBottom: theme.spacing(2),
+  },
+  gridCell: {
+    whiteSpace: 'nowrap',
+    width: '100%',
+    height: '100%',
+    display: 'flex',
+    flexDirection: 'column',
+    paddingLeft: '1em',
+    paddingRight: '1em',
+    overflowX: 'auto',
+    justifyContent: 'center',
+  },
+}));
+
+export default function ResultGrid() {
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const recordings = tab?.recordings || {};
+  const activeRecordingID = tab?.activeRecordingID || '';
+  const result = recordings[activeRecordingID]?.queryOutput;
+  const classes = useStyles();
+
+  function formatDate(d: Date) {
+    let month = `${d.getMonth() + 1}`;
+    let day = `${d.getDate()}`;
+    const year = d.getFullYear();
+
+    if (month.length < 2) month = `0${month}`;
+    if (day.length < 2) day = `0${day}`;
+
+    return [year, month, day].join('-');
+  }
+
+  function format(index: number, entry: unknown, types: number[]): string {
+    let datatype = 0;
+    if (types) {
+      datatype = types[index];
+    }
+    if (datatype === 12) {
+      if (typeof entry === 'number') {
+        const date = new Date(Number(entry));
+        return formatDate(date);
+      }
+      const date = new Date(String(entry));
+      return formatDate(date);
+    }
+    return String(entry);
+  }
+
+  if (!result || !result.results) {
+    return (
+      <Typography style={{ padding: '8px 16px' }} variant="subtitle1">
+        No data to show
+      </Typography>
+    );
+  }
+
+  const { labels, types, values } = result.results;
+  const { columnWidths } = result;
+
+  const STYLE: CSSProperties = {
+    width: '100%',
+    fontFamily: 'raleway',
+    resize: 'none',
+  };
+
+  type CellRendererType = {
+    rowIndex: number;
+    columnIndex: number;
+    style: React.CSSProperties;
+  };
+
+  function cellRenderer({ rowIndex, columnIndex, style }: CellRendererType) {
+    const content =
+      rowIndex === 0
+        ? labels[columnIndex]
+        : format(columnIndex, values[rowIndex - 1][columnIndex], types);
+    return (
+      <div
+        style={style}
+        key={`${columnIndex}-${rowIndex}`}
+        className={classes.gridCell}
+      >
+        {content}
+      </div>
+    );
+  }
+
+  function getColumnWidth({ index }: { index: number }) {
+    console.log(columnWidths);
+    return Math.max(columnWidths ? columnWidths[index] : 100, 50);
+  }
+
+  return (
+    <div className={classes.tableWrapper}>
+      <div
+        style={{
+          minHeight: 201,
+          height: '100%',
+          width: '100%',
+          paddingBottom: 1,
+        }}
+      >
+        <AutoSizer>
+          {({ height, width }) => (
+            <MultiGrid
+              cellRenderer={cellRenderer}
+              columnWidth={getColumnWidth}
+              columnCount={labels.length}
+              height={height}
+              rowHeight={48}
+              rowCount={values.length + 1}
+              fixedRowCount={1}
+              width={width}
+              style={STYLE}
+              styleTopRightGrid={{
+                fontWeight: 'bold',
+                width: '100%',
+                backgroundColor: '#404040',
+                fontSize: 14,
+              }}
+              styleBottomRightGrid={{
+                outline: 'none',
+              }}
+              overscanColumnCount={0}
+              overscanRowCount={2}
+            />
+          )}
+        </AutoSizer>
+      </div>
+    </div>
+  );
+}
diff --git a/app/components/ResultJson.tsx b/app/components/results/ResultJson.tsx
similarity index 100%
rename from app/components/ResultJson.tsx
rename to app/components/results/ResultJson.tsx
diff --git a/app/components/ResultTable.tsx b/app/components/results/ResultTable.tsx
similarity index 88%
rename from app/components/ResultTable.tsx
rename to app/components/results/ResultTable.tsx
index 57638a6dbb93cc492d4434fc3c35859efe745ee1..1fb25530a78313a8452649d0134d2a2c7ec7e8ee 100644
--- a/app/components/ResultTable.tsx
+++ b/app/components/results/ResultTable.tsx
@@ -4,15 +4,16 @@ import { TableCell, Typography } from '@material-ui/core';
 import { AutoSizer, Column, Table } from 'react-virtualized';
 import { uniqueId } from 'lodash';
 import clsx from 'clsx';
-import { RecordingContext } from '../context/RecordingContext';
+import { TabContext } from '../../context/TabContext';
 
 const useStyles = makeStyles((theme) => ({
   tableWrapper: {
     minHeight: 200,
     height: '100%',
     width: '100%',
-    resize: 'vertical',
-    overflowY: 'scroll',
+    resize: 'both',
+    overflowY: 'auto',
+    overflowX: 'hidden',
     scrollbarWidth: 'none',
     paddingBottom: theme.spacing(2),
   },
@@ -30,12 +31,10 @@ const useStyles = makeStyles((theme) => ({
     flex: '1 1 !important',
     textOverflow: 'ellipsis',
     whiteSpace: 'nowrap',
-    overflow: 'hidden',
     '& .ReactVirtualized__Table__rowColumn': {
       flex: '1 1 !important',
       textOverflow: 'ellipsis',
       whiteSpace: 'nowrap',
-      overflow: 'hidden',
     },
   },
   tableRowHover: {
@@ -47,7 +46,7 @@ const useStyles = makeStyles((theme) => ({
     flex: '1 1 !important',
     textOverflow: 'ellipsis',
     whiteSpace: 'nowrap',
-    overflow: 'hidden',
+    overflowY: 'hidden',
   },
 }));
 
@@ -63,12 +62,15 @@ const StyledTableCell = withStyles(() =>
 )(TableCell);
 
 export default function ResultTable() {
-  const recordingContext = useContext(RecordingContext);
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const recordings = tab?.recordings || {};
+  const activeRecordingID = tab?.activeRecordingID || '';
+  const result = recordings[activeRecordingID]?.queryOutput;
 
   const classes = useStyles();
 
-  const result = recordingContext?.activeRecording?.queryOutput;
-
   if (!result || !result.results) {
     return (
       <Typography style={{ padding: '8px 16px' }} variant="subtitle1">
@@ -77,7 +79,7 @@ export default function ResultTable() {
     );
   }
 
-  const { labels, types, values } = result.results;
+  const { labels, types, values } = result?.results;
 
   function formatDate(d: Date) {
     let month = `${d.getMonth() + 1}`;
@@ -152,6 +154,7 @@ export default function ResultTable() {
               headerHeight={48}
               gridStyle={{
                 direction: 'inherit',
+                outline: 'none',
               }}
               rowCount={values.length}
               estimatedColumnSize={100}
diff --git a/app/components/results/TreeViewExplainAnalyze.tsx b/app/components/results/TreeViewExplainAnalyze.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6cde7db2253263edaa67e6990a0bc184fc64ca32
--- /dev/null
+++ b/app/components/results/TreeViewExplainAnalyze.tsx
@@ -0,0 +1,63 @@
+import React, { useContext } from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import TreeView from '@material-ui/lab/TreeView';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import ChevronRightIcon from '@material-ui/icons/ChevronRight';
+import TreeItem from '@material-ui/lab/TreeItem';
+import { Typography } from '@material-ui/core';
+import { ExplainAnalyzeNode } from '../../../backend/data-processor/types/ExplainAnalyzeNode';
+import { TabContext } from '../../context/TabContext';
+
+const useStyles = makeStyles((theme) => ({
+  root: {
+    flexGrow: 1,
+  },
+  header: {
+    padding: theme.spacing(1),
+  },
+}));
+
+export default function RecursiveTreeView() {
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const recordings = tab?.recordings || {};
+  const activeRecordingID = tab?.activeRecordingID || '';
+  const data = recordings[activeRecordingID]?.explainAnalyzeTree;
+
+  const classes = useStyles();
+
+  const RenderTree = (nodes: ExplainAnalyzeNode) => (
+    <TreeItem key={nodes.id} nodeId={nodes.id} label={nodes.name}>
+      {Array.isArray(nodes.children)
+        ? nodes.children.map((node) => RenderTree(node))
+        : null}
+    </TreeItem>
+  );
+
+  const RenderTreeWithoutRoot = (root?: ExplainAnalyzeNode) => {
+    // Renders only the children of root
+    const elements: JSX.Element[] = [];
+    root?.children.forEach((child) => {
+      elements.push(RenderTree(child));
+    });
+    return elements;
+  };
+
+  // TODO: Find dynamic solution to defaultExpanded
+  return (
+    <div>
+      <div className={classes.header}>
+        <Typography variant="h6">Explain Analyze Tree View</Typography>
+      </div>
+      <TreeView
+        className={classes.root}
+        defaultCollapseIcon={<ExpandMoreIcon />}
+        // defaultExpanded={['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']}
+        defaultExpandIcon={<ChevronRightIcon />}
+      >
+        {[RenderTreeWithoutRoot(data)]}
+      </TreeView>
+    </div>
+  );
+}
diff --git a/app/components/tabs/Tab.tsx b/app/components/tabs/Tab.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4605378da30467717a03ece32933897e72fad230
--- /dev/null
+++ b/app/components/tabs/Tab.tsx
@@ -0,0 +1,89 @@
+import {
+  Button,
+  Divider,
+  Icon,
+  IconButton,
+  makeStyles,
+  Tooltip,
+} from '@material-ui/core';
+import React from 'react';
+
+// Need to accept width before making styles, therefore a double arrow function
+const useStyles = (width: number, active: boolean, canClose: boolean) =>
+  makeStyles((theme) => ({
+    wrapper: {
+      display: 'flex',
+      flexFlow: 'row nowrap',
+      alignItems: 'center',
+      justifyItems: 'center',
+      flexGrow: 1,
+      flexShrink: 0,
+      flexBasis: width,
+      maxWidth: width,
+    },
+    labelButton: {
+      flexGrow: 1,
+      maxWidth: width - 30 - 30 - 4, // option button, close button, divider margin
+      minWidth: 30,
+    },
+    labelButtonText: {
+      maxWidth: width - 30 - 30 - 4 - 16,
+      color: active ? 'rgba(255,255,255,0.9)' : 'rgba(255,255,255,0.6)',
+      display: 'inline-block',
+      textOverflow: 'ellipsis',
+      whiteSpace: 'nowrap',
+      overflow: 'hidden',
+    },
+    closeButton: {
+      color:
+        active && canClose ? 'rgba(255,255,255,0.9)' : 'rgba(255,255,255,0.6)',
+    },
+    iconButton: {
+      color: active ? 'rgba(255,255,255,0.9)' : 'rgba(255,255,255,0.6)',
+    },
+    endDivider: {
+      marginLeft: 4,
+    },
+  }));
+
+interface TabProps {
+  label: string;
+  uuid: string;
+  isActive: boolean;
+  width: number;
+  canClose: boolean;
+  onSwitch: (uuid: string) => void;
+  onClose: (uuid: string) => void;
+}
+
+export default function Tab(props: TabProps) {
+  const { label, uuid, isActive, width, canClose, onSwitch, onClose } = props;
+
+  // Passing in max width of a tab
+  const classes = useStyles(width, isActive, canClose)();
+
+  return (
+    <div className={classes.wrapper}>
+      <Tooltip title={label} enterDelay={500} enterNextDelay={500}>
+        <Button
+          className={classes.labelButton}
+          classes={{ label: classes.labelButtonText }}
+          onClick={() => onSwitch(uuid)}
+        >
+          {label}
+        </Button>
+      </Tooltip>
+      <IconButton size="small">
+        <Icon className={classes.iconButton}>more_vert</Icon>
+      </IconButton>
+      <IconButton
+        size="small"
+        onClick={() => onClose(uuid)}
+        disabled={!canClose || !isActive}
+      >
+        <Icon className={classes.closeButton}>clear</Icon>
+      </IconButton>
+      <Divider className={classes.endDivider} orientation="vertical" flexItem />
+    </div>
+  );
+}
diff --git a/app/components/tabs/TabBar.tsx b/app/components/tabs/TabBar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..256b65b163998b15c8a449f4c64370c8c3343044
--- /dev/null
+++ b/app/components/tabs/TabBar.tsx
@@ -0,0 +1,174 @@
+import { Icon, IconButton, makeStyles } from '@material-ui/core';
+import { keys, max, min, valuesIn } from 'lodash';
+import React, { useContext, useEffect, useRef, useState } from 'react';
+import clsx from 'clsx';
+import { TabContext } from '../../context/TabContext';
+import Tab from './Tab';
+import noScrollStyles from './no-scrollbar.module.css';
+
+const useStyles = (tabWidth: number, tabsWidth: number) =>
+  makeStyles((theme) => ({
+    wrapper: {
+      display: 'flex',
+      flexFlow: 'row nowrap',
+      alignItems: 'center',
+      justifyItems: 'center',
+      maxHeight: 40,
+      backgroundColor: theme.palette.primary.dark,
+    },
+    tabsWrapper: {
+      display: 'flex',
+      flexFlow: 'column nowrap',
+      alignItems: 'flex-start',
+      justifyItems: 'center',
+      maxHeight: 40,
+      maxWidth: tabsWidth + 38,
+      overflow: 'scroll',
+    },
+    tabs: {
+      display: 'flex',
+      flexFlow: 'row nowrap',
+      alignItems: 'center',
+      justifyItems: 'flex-start',
+    },
+    endButtons: {
+      display: 'flex',
+      flexFlow: 'row nowrap',
+      flexGrow: 1,
+      justifyContent: 'space-between',
+      marginLeft: theme.spacing(1),
+    },
+    activeIndicatorWrapper: {
+      display: 'inline-block',
+      // flexFlow: 'row nowrap',
+      width: '100%',
+    },
+    activeIndicator: {
+      flexGrow: 1,
+      flexShrink: 1,
+      flexBasis: tabWidth,
+      maxWidth: tabWidth,
+      minWidth: tabWidth,
+      height: 2,
+      minHeight: 2,
+      marginTop: 1,
+      backgroundColor: theme.palette.primary.main,
+      transitionProperty: 'margin-left',
+      transitionDuration: '0.2s',
+    },
+  }));
+
+export default function TabBar() {
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID, displayTabs, tabs } = tabState;
+  const tab = tabs[activeTabID];
+
+  const [activeTabIndex, setActiveTabIndex] = useState(0);
+  const [width, setWidth] = useState(0);
+  // const [maximized, setMaximized] = useState(true);
+
+  const wrapperRef = useRef<HTMLDivElement>(null);
+  const tabsWrapperRef = useRef<HTMLDivElement>(null);
+
+  const availableWidth = width - 38 - 360;
+  const numberOfTabs = keys(displayTabs).length;
+  const fullTabWidth = 196;
+  const minTabWidth = 110;
+  const isFull = availableWidth < numberOfTabs * fullTabWidth;
+
+  const tabWidth = !isFull
+    ? fullTabWidth
+    : max([
+        min([availableWidth / keys(displayTabs).length, fullTabWidth]),
+        minTabWidth,
+      ]) || 0;
+
+  useEffect(() => {
+    const tabIndex = activeTabID
+      ? valuesIn(displayTabs).findIndex((value) => value.tabID === activeTabID)
+      : 0;
+    setActiveTabIndex(tabIndex);
+    if (tabsWrapperRef.current && isFull) {
+      // Scroll to the active tab
+      // Item 0 is the div containing the tabs
+      // Item tabIndex is the active tab within item 0
+      tabsWrapperRef.current.children
+        .item(0)
+        ?.children.item(tabIndex)
+        ?.scrollIntoView({ behavior: 'auto' });
+    }
+  }, [activeTabID, displayTabs]);
+
+  useEffect(() => {
+    const nextWidth = wrapperRef?.current
+      ? wrapperRef?.current?.offsetWidth
+      : 0;
+    setWidth(nextWidth);
+  }, [wrapperRef.current]);
+
+  // function toggleMaximized() {
+  //   const window = remote?.getCurrentWindow();
+  //   if (window?.isMaximized()) {
+  //     window?.unmaximize();
+  //     setMaximized(false);
+  //   } else {
+  //     window?.maximize();
+  //     setMaximized(true);
+  //   }
+  // }
+
+  const classes = useStyles(tabWidth, availableWidth)();
+
+  return (
+    <div
+      className={clsx(classes.wrapper, noScrollStyles.appbar)}
+      ref={wrapperRef}
+    >
+      <div
+        className={clsx(classes.tabsWrapper, noScrollStyles.tabswrapper)}
+        ref={tabsWrapperRef}
+      >
+        <div className={classes.tabs}>
+          {valuesIn(displayTabs).map((displayTab) => (
+            <Tab
+              key={displayTab.tabID}
+              label={displayTab.tabLabel}
+              uuid={displayTab.tabID}
+              isActive={activeTabID === displayTab.tabID}
+              width={tabWidth}
+              canClose={
+                valuesIn(displayTabs).length > 1 && !tab.recordingRunning
+              }
+              onSwitch={(id) =>
+                dispatch({ type: 'SET_ACTIVE_TAB', payload: id })
+              }
+              onClose={(id) => dispatch({ type: 'DELETE_TAB', payload: id })}
+            />
+          ))}
+        </div>
+        <div className={classes.activeIndicatorWrapper}>
+          <div
+            className={classes.activeIndicator}
+            style={{ marginLeft: tabWidth * activeTabIndex }}
+          />
+        </div>
+      </div>
+      <div className={classes.endButtons}>
+        <IconButton size="small" onClick={() => dispatch({ type: 'ADD_TAB' })}>
+          <Icon>add</Icon>
+        </IconButton>
+        {/* <div style={{ alignSelf: 'flex-end' }}>
+          <IconButton size="small" onClick={() => {}}>
+            <Icon>minimize</Icon>
+          </IconButton>
+          <IconButton size="small" onClick={() => toggleMaximized()}>
+            {maximized ? <Icon>fullscreen_exit</Icon> : <Icon>fullscreen</Icon>}
+          </IconButton>
+          <IconButton size="small" onClick={() => {}}>
+            <Icon>clear</Icon>
+          </IconButton>
+        </div> */}
+      </div>
+    </div>
+  );
+}
diff --git a/app/components/tabs/css.d.ts b/app/components/tabs/css.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..af3d030048bbf98d7e4f7a52a75aff894e2aa0d5
--- /dev/null
+++ b/app/components/tabs/css.d.ts
@@ -0,0 +1,9 @@
+declare module '*.scss' {
+  const content: { [className: string]: string };
+  export default content;
+}
+
+declare module '*.css' {
+  const content: { [className: string]: string };
+  export default content;
+}
diff --git a/app/components/tabs/no-scrollbar.module.css b/app/components/tabs/no-scrollbar.module.css
new file mode 100644
index 0000000000000000000000000000000000000000..e3eaa0f2e51c25f6fa9c05834f646c7f615433fa
--- /dev/null
+++ b/app/components/tabs/no-scrollbar.module.css
@@ -0,0 +1,7 @@
+.tabswrapper::-webkit-scrollbar {
+  display: none;
+}
+
+.appbar {
+  -webkit-app-region: drag;
+}
diff --git a/app/containers/Dashboard.tsx b/app/containers/Dashboard.tsx
index c719061ba04b177d61ccdf1430d987d0fcf23bf8..7fec91b7b24fa8228c1191987f1cac9bb56496bc 100644
--- a/app/containers/Dashboard.tsx
+++ b/app/containers/Dashboard.tsx
@@ -1,16 +1,18 @@
-import React, { useContext, useEffect, useState } from 'react';
+import React, { useContext, useState } from 'react';
 import { Button, Dialog, makeStyles } from '@material-ui/core';
-import { useLocation, useHistory } from 'react-router';
 import PanelGroup from 'react-panelgroup';
-import QueryRecorder from '../components/QueryRecorder';
-import routes from '../routes';
-import Login from '../components/Login';
-import { LoginDetailsContext } from '../context/LoginDetailsContext';
-import { RecordingProvider } from '../context/RecordingContext';
+import { v4 as uuid } from 'uuid';
+import QueryRecorder from '../components/dashboard/QueryRecorder';
+import Login from '../components/login/Login';
 import ResultView from './ResultView';
-import Recordings from '../components/Recordings';
-import ErrorView from '../components/ErrorView';
-import IDInfo from '../components/IDInfo';
+import Recordings from '../components/dashboard/Recordings';
+import ErrorView from '../components/dashboard/ErrorView';
+import IDInfo from '../components/dashboard/IDInfo';
+import { TabContext } from '../context/TabContext';
+import { GlobalSnackbarContext } from '../context/GlobalSnackbarContext';
+import { Recording } from '../types/Recording';
+import { RecordingUpdate } from '../types/RecordingUpdate';
+import { RecordingListItem } from '../types/RecordingListItem';
 
 const useStyles = makeStyles((theme) => ({
   wrapper: {
@@ -21,7 +23,7 @@ const useStyles = makeStyles((theme) => ({
   column: {
     display: 'flex',
     flexFlow: 'column nowrap',
-    maxHeight: '98vh',
+    maxHeight: window.innerHeight - 56,
     height: '100%',
     padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
     width: '100%',
@@ -35,72 +37,119 @@ const useStyles = makeStyles((theme) => ({
 export default function Dashboard() {
   const [changeConnectionOpen, setChangeConnectionOpen] = useState(false);
 
-  const loginDetailsContext = useContext(LoginDetailsContext);
-
-  const location = useLocation();
-  const history = useHistory();
-
-  useEffect(() => {
-    if (
-      location.pathname === routes.dashboard &&
-      !changeConnectionOpen &&
-      !loginDetailsContext?.loginDetails
-    ) {
-      history.push(routes.login);
-    }
-  }, []);
+  const { dispatch } = useContext(TabContext);
+  const globalSnackbarContext = useContext(GlobalSnackbarContext);
 
   function openConnectionDialog() {
     setChangeConnectionOpen(true);
   }
 
+  function handleNewRecording(recording: RecordingUpdate) {
+    const {
+      result,
+      error,
+      explainAnalyze,
+      query,
+      label,
+      elapsed,
+      color,
+      tabID,
+    } = recording;
+    const recordingID = uuid();
+    const tempChartData = result?.memoryPerformance || [];
+    const chartDataLength = tempChartData?.length || 0;
+    const totalTime =
+      tempChartData[chartDataLength - 1]?.relative_time * 1000 || NaN;
+    const newRecording: Recording = {
+      queryOutput: result?.result || {},
+      error,
+      stageTimes: result?.stageTimes,
+      chartData: result?.memoryPerformance,
+      optimizerTrace: result?.optimizerTrace,
+      chartColors: result?.chartColors,
+      explainAnalyze:
+        explainAnalyze || query.toLowerCase().includes('explain analyze'),
+      explainAnalyzeTree: result?.explainAnalyze,
+      label,
+      uuid: recordingID,
+      isSaved: false,
+      query,
+      elapsed: totalTime || elapsed || -1,
+      top3Memory: result?.top3Memory,
+    };
+    let formattedLabel;
+    formattedLabel = error ? 'Error' : '';
+    formattedLabel = label.length ? label : formattedLabel;
+    const newListItem: RecordingListItem = {
+      query,
+      elapsed: totalTime || elapsed || -1,
+      label: formattedLabel,
+      color,
+      uuid: recordingID,
+      viewing: true,
+      isSaved: false,
+    };
+    dispatch({
+      type: 'ADD_RECORDING',
+      payload: {
+        tabID: tabID || '',
+        recording: newRecording,
+        recordingListItem: newListItem,
+        recordingID,
+      },
+    });
+    globalSnackbarContext?.setPopupMessage(
+      `Recording finished in ${(elapsed || -1).toFixed(1)} ms`
+    );
+    globalSnackbarContext?.setPopupSeverity('success');
+    globalSnackbarContext?.setPopupOpen(true);
+  }
+
   const classes = useStyles();
 
   return (
     <div className={classes.wrapper}>
-      <RecordingProvider>
-        <PanelGroup
-          borderColor="#4B4B4B"
-          spacing={16}
-          panelWidths={[
-            { minSize: 200, size: 700, resize: 'dynamic' },
-            { minSize: 200, resize: 'dynamic' },
-          ]}
-        >
-          <div className={classes.column}>
-            <div className={classes.item}>
-              <Button
-                variant="outlined"
-                color="primary"
-                onClick={() => openConnectionDialog()}
-              >
-                Change connection
-              </Button>
-              <Dialog
-                open={changeConnectionOpen}
-                onClose={() => setChangeConnectionOpen(false)}
-              >
-                <Login onConnect={() => setChangeConnectionOpen(false)} />
-              </Dialog>
-            </div>
-            <div className={classes.item}>
-              <IDInfo />
-            </div>
-            <div className={classes.item}>
-              <QueryRecorder />
-            </div>
-            <div className={classes.item}>
-              <ErrorView />
-            </div>
-            <div className={classes.item}>
-              <Recordings />
-            </div>
+      <PanelGroup
+        borderColor="#404040"
+        spacing={16}
+        panelWidths={[
+          { minSize: 200, size: 700, resize: 'dynamic' },
+          { minSize: 200, resize: 'dynamic' },
+        ]}
+      >
+        <div className={classes.column}>
+          <div className={classes.item}>
+            <Button
+              variant="outlined"
+              color="primary"
+              onClick={() => openConnectionDialog()}
+            >
+              Change connection
+            </Button>
+            <Dialog
+              open={changeConnectionOpen}
+              onClose={() => setChangeConnectionOpen(false)}
+            >
+              <Login onConnect={() => setChangeConnectionOpen(false)} />
+            </Dialog>
+          </div>
+          <div className={classes.item}>
+            <IDInfo />
+          </div>
+          <div className={classes.item}>
+            <QueryRecorder onNewRecording={handleNewRecording} />
+          </div>
+          <div className={classes.item}>
+            <ErrorView />
           </div>
-          <div className={classes.column}>
-            <ResultView />
+          <div className={classes.item}>
+            <Recordings />
           </div>
-        </PanelGroup>
-      </RecordingProvider>
+        </div>
+        <div className={classes.column}>
+          <ResultView />
+        </div>
+      </PanelGroup>
     </div>
   );
 }
diff --git a/app/containers/LoginScreen.tsx b/app/containers/LoginScreen.tsx
index fe128ae2dec5206c4fd29c3ca7accc2d3a7e92b7..7e1a087e8363d106dadebef021d1f6cc44480426 100644
--- a/app/containers/LoginScreen.tsx
+++ b/app/containers/LoginScreen.tsx
@@ -1,6 +1,6 @@
 import React, { useEffect, useState, useContext } from 'react';
 import { Typography, makeStyles } from '@material-ui/core';
-import Login from '../components/Login';
+import Login from '../components/login/Login';
 import FileIO from '../../backend/utils/FileIO';
 import { LoginDetailsContext } from '../context/LoginDetailsContext';
 
diff --git a/app/containers/ResultView.tsx b/app/containers/ResultView.tsx
index d82ef6212ff009785bec41fe9637ab70ffe88fbe..86712e8059557440ee20a0d36f6504fdf3c11b5b 100644
--- a/app/containers/ResultView.tsx
+++ b/app/containers/ResultView.tsx
@@ -7,14 +7,14 @@ import {
   Tooltip,
 } from '@material-ui/core';
 import React, { useContext, useState } from 'react';
-import { FlameGraph } from 'react-flame-graph';
-import MemoryChart from '../components/MemoryChart';
-import OptimizerTrace from '../components/OptimizerTrace';
-import ResultTable from '../components/ResultTable';
-import { RecordingContext } from '../context/RecordingContext';
-import TreeViewExplainAnalyze from '../components/TreeViewExplainAnalyze';
-import ExplainAnalyzeTable from '../components/ExplainAnalyzeTable';
-import { ExplainAnalyzeNode } from '../../backend/data-processor/types/ExplainAnalyzeNode';
+import FlameGraphExplainAnalyze from '../components/results/FlameGraphExplainAnalyze';
+import MemoryPerformance from '../components/results/MemoryPerformance/MemoryPerformance';
+import OptimizerTrace from '../components/results/OptimizerTrace';
+import ResultTable from '../components/results/ResultTable';
+import ResultGrid from '../components/results/ResultGrid';
+import TreeViewExplainAnalyze from '../components/results/TreeViewExplainAnalyze';
+import { TabContext } from '../context/TabContext';
+import { MemoryPerformanceProvider } from '../context/MemoryPerformanceContext';
 
 const useStyles = makeStyles((theme) => ({
   wrapper: {
@@ -33,38 +33,27 @@ const useStyles = makeStyles((theme) => ({
   },
 }));
 
-const emptyNode: ExplainAnalyzeNode = {
-  id: '0',
-  offset: -1,
-  name: 'root',
-  time: '',
-  rows: -1,
-  value: 0,
-  timeFirstRow: 0,
-  timeAllRows: 0,
-  loops: -1,
-  children: [],
-};
-
 export default function ResultView() {
   const [showMemoryChart, setShowMemoryChart] = useState(true);
-  const [showOptimizerTrace, setShowOptimizerTrace] = useState(false);
+  const [showOptimizerTrace, setShowOptimizerTrace] = useState(true);
   const [showResultTable, setShowResultTable] = useState(false);
-  const [showExplainAnalyze, setShowExplainAnalyze] = useState(false);
-  const [selectedNode, setSelectedNode] = useState(emptyNode);
-  const [hoveredNode, setHoveredNode] = useState(emptyNode);
+  const [showExplainAnalyze, setShowExplainAnalyze] = useState(true);
 
-  const recordingContext = useContext(RecordingContext);
+  const { state: tabState } = useContext(TabContext);
+  const { activeTabID, tabs } = tabState;
+  const tab = tabs[activeTabID];
+  const recordings = tab?.recordings || {};
+  const activeRecordingID = tab?.activeRecordingID || '';
 
   const classes = useStyles();
 
   return (
     <div className={classes.wrapper} style={{ minWidth: '200px' }}>
       <div className={classes.item}>
-        <Tooltip title={recordingContext?.activeRecording?.label || ''}>
+        <Tooltip title={recordings[activeRecordingID]?.label || ''}>
           <Typography variant="h4" className={classes.title}>
             {`Results for recording ${
-              recordingContext?.activeRecording?.label || ''
+              recordings[activeRecordingID]?.label || ''
             }`}
           </Typography>
         </Tooltip>
@@ -100,7 +89,7 @@ export default function ResultView() {
             />
           }
         />
-        {recordingContext?.activeRecording?.explainAnalyze && (
+        {recordings[activeRecordingID]?.explainAnalyze && (
           <FormControlLabel
             label="Show explain analyze"
             control={
@@ -117,7 +106,7 @@ export default function ResultView() {
       </div>
       {showResultTable && (
         <Paper className={classes.item}>
-          <ResultTable />
+          <ResultGrid />
         </Paper>
       )}
       {showMemoryChart && (
@@ -125,45 +114,31 @@ export default function ResultView() {
           className={classes.item}
           style={{ paddingBottom: 8, paddingTop: 8 }}
         >
-          <MemoryChart />
+          {/* <MemoryChart /> */}
+          <MemoryPerformanceProvider>
+            <MemoryPerformance />
+          </MemoryPerformanceProvider>
         </Paper>
       )}
-      {showOptimizerTrace && recordingContext?.activeRecording?.optimizerTrace && (
+      {showOptimizerTrace && recordings[activeRecordingID]?.optimizerTrace && (
         <Paper className={classes.item} style={{ padding: 8 }}>
           <OptimizerTrace />
         </Paper>
       )}
       {showExplainAnalyze &&
-        recordingContext?.activeRecording?.explainAnalyzeTree && (
+        recordings[activeRecordingID]?.explainAnalyze &&
+        recordings[activeRecordingID]?.explainAnalyzeTree && (
           <Paper className={classes.item}>
-            <FlameGraph
-              data={recordingContext?.activeRecording?.explainAnalyzeTree}
-              height={200}
-              width={800}
-              disableDefaultTooltips
-              onChange={(node: {
-                source: React.SetStateAction<ExplainAnalyzeNode>;
-              }) => {
-                setSelectedNode(node.source);
-              }}
-              onMouseOver={(
-                event: any,
-                itemData: React.SetStateAction<ExplainAnalyzeNode>
-              ) => {
-                setHoveredNode(itemData);
-              }}
-              onMouseOut={(event: any, itemData: any) => {
-                setHoveredNode(selectedNode);
-              }}
-            />
-            <ExplainAnalyzeTable node={hoveredNode} />
+            <FlameGraphExplainAnalyze />
+          </Paper>
+        )}
+      {showExplainAnalyze &&
+        recordings[activeRecordingID]?.explainAnalyze &&
+        recordings[activeRecordingID]?.explainAnalyzeTree && (
+          <Paper className={classes.item}>
+            <TreeViewExplainAnalyze />
           </Paper>
         )}
-      {showExplainAnalyze && (
-        <Paper className={classes.item}>
-          <TreeViewExplainAnalyze />
-        </Paper>
-      )}
     </div>
   );
 }
diff --git a/app/containers/TabContainer.tsx b/app/containers/TabContainer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cf0b926608c56e06fb12816287103ab31b86d94c
--- /dev/null
+++ b/app/containers/TabContainer.tsx
@@ -0,0 +1,52 @@
+import React, { useContext, useEffect } from 'react';
+import { Switch, Route, useLocation, useHistory } from 'react-router-dom';
+import { LoginDetailsProvider } from '../context/LoginDetailsContext';
+import routes from '../routes';
+import LoginScreen from './LoginScreen';
+import Dashboard from './Dashboard';
+import TabBar from '../components/tabs/TabBar';
+import { TabContext } from '../context/TabContext';
+import { GlobalSnackbarProvider } from '../context/GlobalSnackbarContext';
+import GlobalSnackbar from '../components/dashboard/GlobalSnackbar';
+
+export default function TabContainer() {
+  const { state: tabState, dispatch } = useContext(TabContext);
+  const { activeTabID, manager } = tabState;
+
+  const location = useLocation();
+  const history = useHistory();
+
+  function switchToTab(tabID: string, isNew = false) {
+    const connection = manager.connections[tabID];
+    if (
+      isNew ||
+      (location.pathname === routes.dashboard && !connection?.client)
+    ) {
+      history.push(routes.login);
+    } else if (connection?.client && location.pathname !== routes.dashboard) {
+      history.push(routes.dashboard);
+    }
+  }
+
+  // Need to ensure we are in a legal state at the start (no unconnected dashboard)
+  useEffect(() => {
+    switchToTab(activeTabID);
+  }, [activeTabID]);
+
+  return (
+    <GlobalSnackbarProvider>
+      <GlobalSnackbar />
+      <LoginDetailsProvider>
+        <TabBar />
+        <Switch>
+          <Route exact path={routes.login}>
+            <LoginScreen />
+          </Route>
+          <Route path={routes.dashboard}>
+            <Dashboard />
+          </Route>
+        </Switch>
+      </LoginDetailsProvider>
+    </GlobalSnackbarProvider>
+  );
+}
diff --git a/app/context/GlobalSnackbarContext.tsx b/app/context/GlobalSnackbarContext.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..eebe28af4819b34d04815279c1ccb8113a60a990
--- /dev/null
+++ b/app/context/GlobalSnackbarContext.tsx
@@ -0,0 +1,41 @@
+import React, { useState, createContext } from 'react';
+
+type ChildrenType = {
+  children: React.ReactNode;
+};
+
+type PopupSeverity = 'success' | 'info' | 'warning' | 'error' | undefined;
+
+type ContextType = {
+  popupMessage?: string;
+  popupSeverity: PopupSeverity;
+  popupOpen: boolean;
+  setPopupMessage: (message: string) => void;
+  setPopupSeverity: (severity: PopupSeverity) => void;
+  setPopupOpen: (open: boolean) => void;
+};
+
+export const GlobalSnackbarContext = createContext<ContextType | undefined>(
+  undefined
+);
+
+export const GlobalSnackbarProvider = ({ children }: ChildrenType) => {
+  const [popupMessage, setPopupMessage] = useState<string>();
+  const [popupSeverity, setPopupSeverity] = useState<PopupSeverity>();
+  const [popupOpen, setPopupOpen] = useState<boolean>(false);
+
+  return (
+    <GlobalSnackbarContext.Provider
+      value={{
+        popupMessage,
+        popupSeverity,
+        popupOpen,
+        setPopupMessage,
+        setPopupSeverity,
+        setPopupOpen,
+      }}
+    >
+      {children}
+    </GlobalSnackbarContext.Provider>
+  );
+};
diff --git a/app/context/LoginDetailsContext.tsx b/app/context/LoginDetailsContext.tsx
index b54b0c6d67337d25bd87b3da88a487dc3e891589..b19671204c67a7760b26c8f6c30d244cf5fd8611 100644
--- a/app/context/LoginDetailsContext.tsx
+++ b/app/context/LoginDetailsContext.tsx
@@ -1,5 +1,5 @@
 import React, { useState, createContext } from 'react';
-import { LoginDetails } from '../../backend/utils/LoginDetails';
+import { LoginDetails } from '../../backend/types/LoginDetails';
 
 type ChildrenType = {
   children: React.ReactNode;
diff --git a/app/context/MemoryPerformanceContext.tsx b/app/context/MemoryPerformanceContext.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..eeac8aba31e4d2b8b05ec714b6b94e0b49d02945
--- /dev/null
+++ b/app/context/MemoryPerformanceContext.tsx
@@ -0,0 +1,64 @@
+import React, { useState, createContext } from 'react';
+
+type ChildrenType = {
+  children: React.ReactNode;
+};
+
+type ContextType = {
+  disabled: Array<string>;
+  setDisabled: (value: Array<string>) => void;
+  validStages: Array<string>;
+  setValidStages: (value: Array<string>) => void;
+  openEvent: boolean;
+  setOpenEvent: (value: boolean) => void;
+  openStage: boolean;
+  setOpenStage: (value: boolean) => void;
+  eventsChecked: Array<string>;
+  setEventsChecked: (value: Array<string>) => void;
+  stagesChecked: Array<string>;
+  setStagesChecked: (value: Array<string>) => void;
+  filteredEvents: Array<string>;
+  setFilteredEvents: (value: Array<string>) => void;
+  filteredStages: Array<string>;
+  setFilteredStages: (value: Array<string>) => void;
+};
+
+export const MemoryPerformanceContext = createContext<ContextType | undefined>(
+  undefined
+);
+
+export const MemoryPerformanceProvider = ({ children }: ChildrenType) => {
+  const [disabled, setDisabled] = useState<Array<string>>([]);
+  const [validStages, setValidStages] = useState<Array<string>>([]);
+  const [openEvent, setOpenEvent] = useState(false);
+  const [openStage, setOpenStage] = useState(false);
+  const [eventsChecked, setEventsChecked] = useState<Array<string>>([]);
+  const [stagesChecked, setStagesChecked] = useState<Array<string>>([]);
+  const [filteredEvents, setFilteredEvents] = useState<Array<string>>([]);
+  const [filteredStages, setFilteredStages] = useState<Array<string>>([]);
+
+  return (
+    <MemoryPerformanceContext.Provider
+      value={{
+        disabled,
+        setDisabled,
+        validStages,
+        setValidStages,
+        openEvent,
+        setOpenEvent,
+        openStage,
+        setOpenStage,
+        eventsChecked,
+        setEventsChecked,
+        stagesChecked,
+        setStagesChecked,
+        filteredEvents,
+        setFilteredEvents,
+        filteredStages,
+        setFilteredStages,
+      }}
+    >
+      {children}
+    </MemoryPerformanceContext.Provider>
+  );
+};
diff --git a/app/context/RecordingContext.tsx b/app/context/RecordingContext.tsx
deleted file mode 100644
index 6eae3d1d8ae259d0009a81d3e6ca384f6aecd8a9..0000000000000000000000000000000000000000
--- a/app/context/RecordingContext.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React, { useState, createContext } from 'react';
-import { Recording } from '../types/Recording';
-import { RecordingListItem } from '../types/RecordingListItem';
-
-type ChildrenType = {
-  children: React.ReactNode;
-};
-
-type ContextType = {
-  activeRecording?: Recording;
-  setActiveRecording: (value?: Recording) => void;
-  recordings?: Recording[];
-  setRecordings: (value?: Recording[]) => void;
-  recordingListItems?: RecordingListItem[];
-  setRecordingListItems: (value?: RecordingListItem[]) => void;
-};
-
-export const RecordingContext = createContext<ContextType | undefined>(
-  undefined
-);
-
-export const RecordingProvider = ({ children }: ChildrenType) => {
-  const [activeRecording, setActiveRecording] = useState<Recording | undefined>(
-    undefined
-  );
-  const [recordings, setRecordings] = useState<Recording[] | undefined>([]);
-  const [recordingListItems, setRecordingListItems] = useState<
-    RecordingListItem[] | undefined
-  >([]);
-
-  return (
-    <RecordingContext.Provider
-      value={{
-        activeRecording,
-        setActiveRecording,
-        recordings,
-        setRecordings,
-        recordingListItems,
-        setRecordingListItems,
-      }}
-    >
-      {children}
-    </RecordingContext.Provider>
-  );
-};
diff --git a/app/context/TabContext.tsx b/app/context/TabContext.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d84a62a1661140501299966082f2b228b88704fa
--- /dev/null
+++ b/app/context/TabContext.tsx
@@ -0,0 +1,437 @@
+/* eslint-disable no-case-declarations */
+import { keys, valuesIn } from 'lodash';
+import React, { createContext, useReducer } from 'react';
+import { v4 as uuid } from 'uuid';
+import SqlManager from '../../backend/recorder/SqlManager';
+import SqlManagerSingleton from '../../backend/recorder/SqlManagerSingleton';
+import { DisplayTab } from '../types/DisplayTab';
+import { Recording } from '../types/Recording';
+import { RecordingListItem } from '../types/RecordingListItem';
+import { Tab } from '../types/Tab';
+
+interface ChildrenType {
+  children: React.ReactNode;
+}
+
+export interface RecordingPayload {
+  tabID: string;
+  recordingID?: string;
+  recording?: Recording;
+  recordingListItem?: RecordingListItem;
+  value?: string | number | boolean;
+}
+
+export interface DisplayTabLabelPayload {
+  id: string;
+  label: string;
+}
+
+export interface TabState {
+  activeTabID: string;
+  createdTabs: number;
+  tabs: { [key: string]: Tab };
+  displayTabs: { [key: string]: DisplayTab };
+  manager: SqlManager;
+}
+
+export interface TabAction {
+  type:
+    | 'ADD_RECORDING'
+    | 'DELETE_RECORDING'
+    | 'SET_RECORDING_LABEL'
+    | 'SET_RECORDING_COLOR'
+    | 'SET_ACTIVE_RECORDING'
+    | 'SET_RECORDING_STATE'
+    | 'SET_INPUT_QUERY'
+    | 'SET_SHOW_ADVANCED_OPTIONS'
+    | 'SET_EXPLAIN_ANALYZE'
+    | 'SET_TIMESTEP'
+    | 'SET_RECORDING_INPUT_LABEL'
+    | 'SET_RECORDING_INPUT_COLOR'
+    | 'SET_QUERY_RUNNING'
+    | 'SET_RECORDING_RUNNING'
+    | 'SET_DISPLAYTAB_LABEL'
+    | 'ADD_TAB'
+    | 'SET_ACTIVE_TAB'
+    | 'DELETE_TAB';
+  payload?:
+    | Tab
+    | DisplayTab
+    | string
+    | boolean
+    | number
+    | DisplayTabLabelPayload
+    | RecordingPayload;
+}
+
+const firstTabID = uuid();
+
+const initialState: TabState = {
+  activeTabID: firstTabID,
+  createdTabs: 1,
+  tabs: {
+    [firstTabID]: {
+      tabID: firstTabID,
+      serial: 1,
+      recordings: {},
+      recordingListItems: {},
+      recordingRunning: false,
+      queryRunning: false,
+      inputQuery: '',
+      inputLabel: '',
+      inputColor: '',
+      explainAnalyze: false,
+      timestep: 0.2,
+      showAdvancedOptions: false,
+      recordingState: '',
+    },
+  },
+  displayTabs: {
+    [firstTabID]: { tabID: firstTabID, tabLabel: 'New connection' },
+  },
+  manager: SqlManagerSingleton.getInstance(),
+};
+
+const tabReducer = (tabState: TabState, action: TabAction): TabState => {
+  switch (action.type) {
+    case 'ADD_TAB':
+      const tabID = uuid();
+      const tabLabel = 'New connection';
+      const changedDisplayTabs = { ...tabState.displayTabs };
+      changedDisplayTabs[tabID] = {
+        tabID,
+        tabLabel,
+      };
+      const changedTabs = { ...tabState.tabs };
+      changedTabs[tabID] = {
+        tabID,
+        serial: tabState.createdTabs + 1,
+        recordings: {},
+        recordingListItems: {},
+        recordingRunning: false,
+        queryRunning: false,
+        inputQuery: '',
+        inputLabel: '',
+        inputColor: '',
+        explainAnalyze: false,
+        timestep: 0.2,
+        showAdvancedOptions: false,
+        recordingState: '',
+      };
+      return {
+        ...tabState,
+        tabs: changedTabs,
+        displayTabs: changedDisplayTabs,
+        activeTabID: tabID,
+      };
+    case 'DELETE_TAB':
+      if (typeof action.payload === 'string') {
+        const id = action.payload;
+        const tabs = { ...tabState.tabs };
+        const displayTabs = { ...tabState.displayTabs };
+        delete tabs[id];
+        delete displayTabs[id];
+        const ids = keys(displayTabs);
+        let newIndex = ids.length - 1; // First assuming the tab was the rightmost one
+        if (ids.length < 1) return tabState; // We do not delete the last tab (for now)
+        if (newIndex < 0) newIndex = 0; // Avoid index out of bounds
+        const activeTabID = ids[newIndex];
+        tabState.manager.removeConnection(id);
+        return { ...tabState, activeTabID, tabs, displayTabs };
+      }
+      return tabState;
+    case 'SET_ACTIVE_TAB':
+      if (typeof action.payload === 'string') {
+        const activeTabID = action.payload;
+        return { ...tabState, activeTabID };
+      }
+      return tabState;
+    case 'SET_DISPLAYTAB_LABEL':
+      if (
+        (action.payload as DisplayTabLabelPayload)?.id &&
+        (action.payload as DisplayTabLabelPayload)?.label !== undefined
+      ) {
+        const { id, label } = action.payload as DisplayTabLabelPayload;
+        const displayTabs = { ...tabState.displayTabs };
+        displayTabs[id].tabLabel = label;
+        return { ...tabState, displayTabs };
+      }
+      return tabState;
+    case 'ADD_RECORDING':
+      if (
+        (action.payload as RecordingPayload).recording &&
+        (action.payload as RecordingPayload).recordingListItem &&
+        (action.payload as RecordingPayload).recordingID &&
+        (action.payload as RecordingPayload).tabID
+      ) {
+        const {
+          tabID: id,
+          recording,
+          recordingListItem,
+          recordingID,
+        } = action.payload as RecordingPayload;
+        if (!recording || !recordingListItem || !recordingID) return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        const recordings = { ...tab.recordings };
+        const recordingListItems = { ...tab.recordingListItems };
+        recordings[recordingID] = recording;
+        recordingListItems[recordingID] = recordingListItem;
+        tab.recordings = recordings;
+        tab.recordingListItems = recordingListItems;
+        tab.activeRecordingID = recordingID;
+        tab.inputLabel = '';
+        tab.recordingRunning = false;
+        tab.queryRunning = false;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_ACTIVE_RECORDING':
+      if (
+        (action.payload as RecordingPayload).recordingID &&
+        (action.payload as RecordingPayload).tabID
+      ) {
+        const { tabID: id, recordingID } = action.payload as RecordingPayload;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        tab.activeRecordingID = recordingID;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_RECORDING_STATE':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).value !== undefined
+      ) {
+        const { tabID: id, value } = action.payload as RecordingPayload;
+        if (typeof value !== 'string') return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        tab.recordingState = value;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_INPUT_QUERY':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).value !== undefined
+      ) {
+        const { tabID: id, value } = action.payload as RecordingPayload;
+        if (typeof value !== 'string') return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        tab.inputQuery = value;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_SHOW_ADVANCED_OPTIONS':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).value !== undefined
+      ) {
+        const { tabID: id, value } = action.payload as RecordingPayload;
+        if (typeof value !== 'boolean') return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        tab.showAdvancedOptions = value;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_EXPLAIN_ANALYZE':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).value !== undefined
+      ) {
+        const { tabID: id, value } = action.payload as RecordingPayload;
+        if (typeof value !== 'boolean') return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        tab.explainAnalyze = value;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_TIMESTEP':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).value !== undefined
+      ) {
+        const { tabID: id, value } = action.payload as RecordingPayload;
+        if (typeof value !== 'number') return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        tab.timestep = value;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_RECORDING_INPUT_LABEL':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).value !== undefined
+      ) {
+        const { tabID: id, value } = action.payload as RecordingPayload;
+        if (typeof value !== 'string') return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        tab.inputLabel = value;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_RECORDING_INPUT_COLOR':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).value !== undefined
+      ) {
+        const { tabID: id, value } = action.payload as RecordingPayload;
+        if (typeof value !== 'string') return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        tab.inputColor = value;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_QUERY_RUNNING':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).value !== undefined
+      ) {
+        const { tabID: id, value } = action.payload as RecordingPayload;
+        if (typeof value !== 'boolean') return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        tab.queryRunning = value;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_RECORDING_RUNNING':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).value !== undefined
+      ) {
+        const { tabID: id, value } = action.payload as RecordingPayload;
+        if (typeof value !== 'boolean') return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        tab.recordingRunning = value;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_RECORDING_LABEL':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).recordingID &&
+        (action.payload as RecordingPayload).value !== undefined
+      ) {
+        const {
+          tabID: id,
+          value,
+          recordingID,
+        } = action.payload as RecordingPayload;
+        if (typeof value !== 'string' || !recordingID) return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        const recordings = { ...tab.recordings };
+        const recordingListItems = { ...tab.recordingListItems };
+        recordings[recordingID].label = value;
+        recordingListItems[recordingID].label = value;
+        tab.recordings = recordings;
+        tab.recordingListItems = recordingListItems;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'SET_RECORDING_COLOR':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).recordingID &&
+        (action.payload as RecordingPayload).value !== undefined
+      ) {
+        const {
+          tabID: id,
+          value,
+          recordingID,
+        } = action.payload as RecordingPayload;
+        if (typeof value !== 'string' || !recordingID) return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        const recordingListItems = { ...tab.recordingListItems };
+        recordingListItems[recordingID].color = value;
+        tab.recordingListItems = recordingListItems;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    case 'DELETE_RECORDING':
+      if (
+        (action.payload as RecordingPayload).tabID &&
+        (action.payload as RecordingPayload).recordingID
+      ) {
+        const { tabID: id, recordingID } = action.payload as RecordingPayload;
+        if (!recordingID) return tabState;
+        const tabs = { ...tabState.tabs };
+        const tab = tabs[id];
+        const recordings = { ...tab.recordings };
+        const recordingListItems = { ...tab.recordingListItems };
+        delete recordings[recordingID];
+        delete recordingListItems[recordingID];
+        const listItemsArray = valuesIn(tab.recordingListItems);
+        const index = listItemsArray.findIndex(
+          (item) => item.uuid === recordingID
+        );
+        let activeRecordingID;
+        if (recordingID === tab?.activeRecordingID) {
+          let newIndex = index - 1;
+          if (newIndex < 0) {
+            newIndex = 0;
+          }
+          if (listItemsArray.length < 1) {
+            newIndex = -1;
+          } else if (listItemsArray.length === 1) {
+            newIndex = 0;
+          }
+          if (newIndex !== -1 && listItemsArray[newIndex]) {
+            activeRecordingID =
+              newIndex !== -1 ? listItemsArray[newIndex].uuid : undefined;
+          }
+        }
+        tab.recordingListItems = recordingListItems;
+        tab.recordings = recordings;
+        tab.activeRecordingID = activeRecordingID;
+        tabs[id] = tab;
+        return { ...tabState, tabs };
+      }
+      return tabState;
+    default:
+      return tabState;
+  }
+};
+
+export const TabContext = createContext<{
+  state: TabState;
+  dispatch: React.Dispatch<TabAction>;
+}>({ state: initialState, dispatch: () => null });
+
+export const TabProvider = ({ children }: ChildrenType) => {
+  const [state, dispatch] = useReducer(tabReducer, initialState);
+
+  return (
+    <TabContext.Provider
+      value={{
+        state,
+        dispatch,
+      }}
+    >
+      {children}
+    </TabContext.Provider>
+  );
+};
diff --git a/app/main.dev.ts b/app/main.dev.ts
index 28d4d12c5feee2d2d05a9bce195e6fc1c4dbf38c..153e7254ae57904c97d81f0a7ce5473ece390025 100644
--- a/app/main.dev.ts
+++ b/app/main.dev.ts
@@ -14,13 +14,8 @@ import 'regenerator-runtime/runtime';
 import { app, BrowserWindow } from 'electron';
 import { autoUpdater } from 'electron-updater';
 import log from 'electron-log';
-// import installExtension, {
-//   REACT_DEVELOPER_TOOLS,
-// } from 'electron-devtools-installer';
 import MenuBuilder from './menu';
 
-// app.allowRendererProcessReuse = true;
-
 export default class AppUpdater {
   constructor() {
     log.transports.file.level = 'info';
@@ -36,36 +31,14 @@ if (process.env.NODE_ENV === 'production') {
   sourceMapSupport.install();
 }
 
-// if (
-//   process.env.NODE_ENV === 'development' ||
-//   process.env.DEBUG_PROD === 'true'
-// ) {
-//   require('electron-debug')();
-// }
-
-// const installExtensions = async () => {
-//   const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
-//   const extensions = [REACT_DEVELOPER_TOOLS];
-
-//   return Promise.all(
-//     extensions.map((name) => installExtension(name, forceDownload))
-//   ).catch(console.log);
-// };
-
 const createWindow = async () => {
-  // if (
-  //   process.env.NODE_ENV === 'development' ||
-  //   process.env.DEBUG_PROD === 'true'
-  // ) {
-  //   await installExtensions();
-  // }
-
   mainWindow = new BrowserWindow({
     show: false,
     width: 1080,
     height: 900,
     webPreferences: {
       nodeIntegration: true,
+      nativeWindowOpen: true,
     },
   });
 
diff --git a/app/package.json b/app/package.json
index e5abeef9c5f0262bb9b92dcd07ef46e74369ce60..83d9efbe70c8f20463713e08519086803a245a70 100644
--- a/app/package.json
+++ b/app/package.json
@@ -1,7 +1,7 @@
 {
   "name": "mysql-query-profiler",
   "productName": "MySQL Query Profiler",
-  "version": "0.3.1",
+  "version": "0.4.0",
   "description": "A profiler for MySQL queries using Electron and React",
   "main": "./main.prod.js",
   "author": {
diff --git a/app/smartToolTip.js b/app/smartToolTip.js
new file mode 100644
index 0000000000000000000000000000000000000000..8c474911ecac73585d6bed480d3d3f9e5eafce4a
--- /dev/null
+++ b/app/smartToolTip.js
@@ -0,0 +1,58 @@
+/* eslint-disable linebreak-style */
+/** @flow */
+
+import { useLayoutEffect, useRef } from 'react';
+
+const TOOLTIP_OFFSET = 4;
+
+export default function useSmartTooltip({ mouseX, mouseY }) {
+  // $FlowFixMe Flow generics syntax causes a compilation error with the current Babel version
+  const ref = useRef(null);
+  useLayoutEffect(() => {
+    const element = ref.current;
+    if (element != null) {
+      // Let's check the vertical position.
+      if (
+        mouseY + TOOLTIP_OFFSET + element.offsetHeight >=
+        window.innerHeight
+      ) {
+        // The tooltip doesn't fit below the mouse cursor (which is our
+        // default strategy). Therefore we try to position it either above the
+        // mouse cursor or finally aligned with the window's top edge.
+        if (mouseY - TOOLTIP_OFFSET - element.offsetHeight > 0) {
+          // We position the tooltip above the mouse cursor if it fits there.
+          element.style.top = `${
+            mouseY - element.offsetHeight - TOOLTIP_OFFSET
+          }px`;
+        } else {
+          // Otherwise we align the tooltip with the window's top edge.
+          element.style.top = '0px';
+        }
+      } else {
+        element.style.top = `${mouseY + TOOLTIP_OFFSET}px`;
+      }
+
+      // Now let's check the horizontal position.
+      if (mouseX + TOOLTIP_OFFSET + element.offsetWidth >= window.innerWidth) {
+        // The tooltip doesn't fit at the right of the mouse cursor (which is
+        // our default strategy). Therefore we try to position it either at the
+        // left of the mouse cursor or finally aligned with the window's left
+        // edge.
+        if (mouseX - TOOLTIP_OFFSET - element.offsetWidth > 0) {
+          // We position the tooltip at the left of the mouse cursor if it fits
+          // there.
+          element.style.left = `${
+            mouseX - element.offsetWidth - TOOLTIP_OFFSET
+          }px`;
+        } else {
+          // Otherwise, align the tooltip with the window's left edge.
+          element.style.left = '0px';
+        }
+      } else {
+        element.style.left = `${mouseX + TOOLTIP_OFFSET}px`;
+      }
+    }
+  });
+
+  return ref;
+}
diff --git a/app/types/DisplayTab.ts b/app/types/DisplayTab.ts
new file mode 100644
index 0000000000000000000000000000000000000000..49849ef22610cbac5dde6417888b869a9100dac0
--- /dev/null
+++ b/app/types/DisplayTab.ts
@@ -0,0 +1,4 @@
+export interface DisplayTab {
+  tabID: string; // UUID
+  tabLabel: string;
+}
diff --git a/app/types/Recording.ts b/app/types/Recording.ts
index 890aa775846904ef63efebfaf758663321231539..c032fe1b2712ffb198bf0a04b80986657246fb90 100644
--- a/app/types/Recording.ts
+++ b/app/types/Recording.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { ResultValue } from '@mysql/xdevapi';
 import { ChartColors } from '../../backend/data-processor/types/ChartColors';
 import { ChartData } from '../../backend/data-processor/types/ChartData';
@@ -9,11 +10,15 @@ export interface Recording {
   chartData?: ChartData;
   stageTimes?: StageTimes;
   chartColors?: ChartColors;
+  top3Memory?: string[];
   optimizerTrace?: ResultValue[][];
   queryOutput?: QueryOutput;
   explainAnalyze: boolean;
   explainAnalyzeTree?: ExplainAnalyzeNode;
-  error?: string;
+  error?: any;
   label: string; // On both in order to avoid having to create an 'activeRecordingListItem'
   uuid: string;
+  isSaved: boolean;
+  query: string;
+  elapsed: number;
 }
diff --git a/app/types/RecordingListItem.ts b/app/types/RecordingListItem.ts
index fc61436c2d49e3a2665e798e3f41d3fb30c318eb..3060524611c3a6498d534ce104bc249335780153 100644
--- a/app/types/RecordingListItem.ts
+++ b/app/types/RecordingListItem.ts
@@ -5,4 +5,5 @@ export interface RecordingListItem {
   label: string;
   color: string;
   viewing: boolean;
+  isSaved: boolean;
 }
diff --git a/app/types/RecordingUpdate.ts b/app/types/RecordingUpdate.ts
new file mode 100644
index 0000000000000000000000000000000000000000..20ccd6b57db006bdf2b1d0dd3d060fd244ae0056
--- /dev/null
+++ b/app/types/RecordingUpdate.ts
@@ -0,0 +1,13 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { RawRecording } from '../../backend/recorder/SqlManager';
+
+export interface RecordingUpdate {
+  result?: RawRecording;
+  elapsed?: number;
+  label: string;
+  color: string;
+  error?: any;
+  explainAnalyze: boolean;
+  query: string;
+  tabID?: string;
+}
diff --git a/app/types/Tab.ts b/app/types/Tab.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b0272ccae79f0b08fcb8f358932d955db471325e
--- /dev/null
+++ b/app/types/Tab.ts
@@ -0,0 +1,19 @@
+import { Recording } from './Recording';
+import { RecordingListItem } from './RecordingListItem';
+
+export interface Tab {
+  tabID: string; // UUID
+  serial: number; // How many tabs had been made when this one was made
+  activeRecordingID?: string;
+  recordings: { [key: string]: Recording };
+  recordingListItems: { [key: string]: RecordingListItem };
+  recordingRunning: boolean;
+  queryRunning: boolean;
+  inputQuery: string;
+  timestep: number;
+  explainAnalyze: boolean;
+  inputLabel: string;
+  inputColor: string;
+  showAdvancedOptions: boolean;
+  recordingState: string; // What the manager is doing while recording
+}
diff --git a/app/types/TabRecordingContext.ts b/app/types/TabRecordingContext.ts
new file mode 100644
index 0000000000000000000000000000000000000000..01149796c48d952849416b0e0d6fb0e53b412d61
--- /dev/null
+++ b/app/types/TabRecordingContext.ts
@@ -0,0 +1,9 @@
+import { Recording } from './Recording';
+import { RecordingListItem } from './RecordingListItem';
+
+export interface TabRecordingContext {
+  tabID: string;
+  activeRecordingID?: string;
+  recordings: { [key: string]: Recording };
+  recordingListItems: { [key: string]: RecordingListItem };
+}
diff --git a/backend/data-processor/DataProcessor.ts b/backend/data-processor/DataProcessor.ts
index c3b0b6be4509db3c0499611fd87d0ffc7fbe4e84..edea9117090672abee4fe87abb7c29cb21f1b55b 100644
--- a/backend/data-processor/DataProcessor.ts
+++ b/backend/data-processor/DataProcessor.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-plusplus */
 /* eslint-disable class-methods-use-this */
 import { ResultValue } from '@mysql/xdevapi';
 import lodash from 'lodash';
@@ -59,7 +60,8 @@ export default class DataProcessor {
         timeLine.isBytesUsedZero =
           timeLine.bytesUsed.reduce((a, b) => a + b, 0) === 0;
       });
-      return DataProcessor.getChartData(result);
+      // return DataProcessor.getChartData(result);
+      return result;
     }
     throw new Error('DataProcessor: performanceData had wrong format');
   }
@@ -101,6 +103,45 @@ export default class DataProcessor {
     throw new Error('DataProcessor: stageTimesRaw had wrong format');
   }
 
+  static formatDate(d: Date) {
+    let month = `${d.getMonth() + 1}`;
+    let day = `${d.getDate()}`;
+    const year = d.getFullYear();
+
+    if (month.length < 2) month = `0${month}`;
+    if (day.length < 2) day = `0${day}`;
+
+    return [year, month, day].join('-');
+  }
+
+  static calculateRowWidths({
+    values,
+    labels,
+    types,
+  }: {
+    values: ResultValue[][];
+    labels: string[];
+    types: number[];
+  }): number[] {
+    console.log(values);
+    console.log(labels);
+    const highest = labels.map((label) => label.length);
+    for (let i = 0; i < values.length; i++) {
+      const currentObject = values[0];
+      for (let j = 0; j < currentObject.length; j++) {
+        const datatype = types[j];
+        highest[j] = Math.max(
+          highest[j],
+          datatype === 12
+            ? DataProcessor.formatDate(new Date(Number(currentObject[j])))
+                .length
+            : String(currentObject[j]).length
+        );
+      }
+    }
+    return highest.map((stringlength) => stringlength * 15);
+  }
+
   static processOptimizerTrace(optimizerTrace?: Result) {
     let traceResult;
     if (optimizerTrace && optimizerTrace.values) {
@@ -130,12 +171,23 @@ export default class DataProcessor {
     return chartColors;
   }
 
+  static processTop3ChartData(memoryPerformance: MemoryPerformance) {
+    const sortedChartData = lodash.sortBy(memoryPerformance, (o) =>
+      lodash.sum(o.bytesUsed)
+    );
+    const top3ChartData = lodash.takeRight(sortedChartData, 3);
+    const top3List: string[] = [];
+    lodash.forEach(top3ChartData, (o) => top3List.push(o.eventName));
+    return top3List;
+  }
+
   // -> Limit: 10 row(s)  (actual time=0.098..0.103 rows=10 loops=1)
   //   -> Table scan on employees  (cost=30192.25 rows=299600) (actual time=0.095..0.099 rows=10 loops=1)
 
   // " Table scan on ints  (actual time=0.006..0.029 rows=5 loops=1)"
   // " Materialize  (actual time=2.662..2.697 rows=5 loops=1)       "
   // " Rows fetched before execution  (actual time=0.00
+
   static processExplainAnalyze(explainAnalyze?: Result) {
     if (
       !explainAnalyze ||
@@ -155,6 +207,7 @@ export default class DataProcessor {
       timeFirstRow: 0,
       timeAllRows: 0,
       loops: -1,
+      additionalData: [],
       children: [],
     };
     const regexp = /([ ]*)?-> (.+?)( \(cost=([\d.]+) rows=(\d+)\))? \(actual time=([\d.]+) rows=(\d+) loops=(\d+)\)/gm;
@@ -166,9 +219,6 @@ export default class DataProcessor {
     matches.forEach((element) => {
       const timeResult = element[6].match(regexpTime);
       if (timeResult != null) {
-        const timeFirst = Number(timeResult[1]) * Number(element[8]);
-        const timeAll = Number(timeResult[2]) * Number(element[8]);
-
         const node: ExplainAnalyzeNode = {
           id: idIterator.toString(),
           offset: element[1] === undefined ? 0 : element[1].length,
@@ -176,13 +226,16 @@ export default class DataProcessor {
           cost_est: Number(element[4]),
           rows_est: Number(element[5]),
           time: element[6],
-          value: timeAll,
-          timeFirstRow: timeFirst,
-          timeAllRows: timeAll,
+          // Value = Total time = Time all rows * loops
+          value: Number(timeResult[2]) * Number(element[8]),
+          timeFirstRow: Number(timeResult[1]),
+          timeAllRows: Number(timeResult[2]),
           rows: Number(element[7]),
           loops: Number(element[8]),
+          additionalData: [],
           children: [],
         };
+        console.log(node.time, node.timeAllRows, node.loops, node.value);
         nodes.push(node);
         idIterator += 1;
       }
@@ -190,25 +243,25 @@ export default class DataProcessor {
 
     // Assign children of each object
     // Iterate through all objects
-    for (let i = 1; i < nodes.length; i += 1) {
-      // Set current node as child if its offset is smaller
-      if (nodes[i - 1].offset < nodes[i].offset) {
-        nodes[i - 1].children.push(nodes[i]);
+    nodes.forEach((node, index) => {
+      if (index === 0) return;
+      if (nodes[index - 1].offset < node.offset) {
+        nodes[index - 1].children.push(node);
       } else {
         // If current node's offset is smaller then it cannot be child of the previous node
         // Iterate backwards from current node until a node with smaller offset has been reached
-        for (let j = i - 1; i > 0; j -= 1) {
-          if (nodes[j].offset < nodes[i].offset) {
-            nodes[j].children.push(nodes[i]);
+        for (let j = index - 1; index > 0; j -= 1) {
+          if (nodes[j].offset < nodes[index].offset) {
+            nodes[j].children.push(nodes[index]);
             break;
           }
         }
       }
-    }
+    });
 
     const node = DataProcessor.fixNodeTimes(nodes[0]);
 
-    DataProcessor.log(explainText);
+    // DataProcessor.log(explainText);
     return node;
   }
 
@@ -218,6 +271,7 @@ export default class DataProcessor {
    * @param node
    */
   static fixNodeTimes(node: ExplainAnalyzeNode) {
+    // const threshold = 0.05;
     if (!node.children.length) {
       return node;
     }
@@ -226,8 +280,25 @@ export default class DataProcessor {
       const nextNode = DataProcessor.fixNodeTimes(element);
       sumChildren += nextNode.value;
     });
+    // If parent nodes's total time (value) is a certain degree (threshold) smaller than sum of childrens' times,
+    // then parent's total time is set to THE SAME as childrens' total time
+    // If it's smaller than that the childrens' total time is ADDED to parent's total time
+    /* if (node.value < sumChildren * (1 - threshold)) {
+      const prev = String(node.value);
+      node.value += sumChildren;
+      console.log(
+        `${node.name} total time changed from ${prev} to ${String(
+          node.value
+        )} (added to old value)`
+      );
+    } else */
     if (node.value < sumChildren) {
+      const prev = node.value;
       node.value += sumChildren;
+      node.additionalData.push({
+        description: 'Total time change',
+        data: `Total time changed from ${prev} to ${String(node.value)}`,
+      });
     }
     return node;
   }
diff --git a/backend/data-processor/types/ExplainAnalyzeNode.ts b/backend/data-processor/types/ExplainAnalyzeNode.ts
index 2e5706165df24e59e6e4021758cb2d11b3b0d14c..7dced18ad9dbbf75c82ee426626036af1454ec38 100644
--- a/backend/data-processor/types/ExplainAnalyzeNode.ts
+++ b/backend/data-processor/types/ExplainAnalyzeNode.ts
@@ -1,7 +1,8 @@
+import { TableElement } from './TableElement';
+
 export interface ExplainAnalyzeNode {
   id: string;
   offset: number;
-  // Endret fra command til name, siden koponenten krever det
   name: string;
   cost_est?: number;
   rows_est?: number;
@@ -11,5 +12,7 @@ export interface ExplainAnalyzeNode {
   timeAllRows: number;
   rows: number;
   loops: number;
+  // Each object in additionalData is added to the table associated to each node
+  additionalData: TableElement[];
   children: ExplainAnalyzeNode[];
 }
diff --git a/backend/data-processor/types/TableElement.ts b/backend/data-processor/types/TableElement.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8dcb78e25923e225591028a879fd0900833a4e59
--- /dev/null
+++ b/backend/data-processor/types/TableElement.ts
@@ -0,0 +1,4 @@
+export interface TableElement {
+  description: string;
+  data: string;
+}
diff --git a/backend/recorder/SqlAgent.ts b/backend/recorder/SqlAgent.ts
index 5bc7eed2007d9833766d8a0ca536cdc3ad4b5290..b1a418eb685d5d13430e65e9f19acea784123e6f 100644
--- a/backend/recorder/SqlAgent.ts
+++ b/backend/recorder/SqlAgent.ts
@@ -70,11 +70,13 @@ export default class SqlAgent {
     await sqlMonitor.connect(client, database);
   }
 
-  async killQuery(connectionID: string) {
+  async killQuery(connectionID?: string) {
+    if (!connectionID) return { results: undefined, error: undefined };
     return this.executeQuery(`KILL QUERY ${connectionID}`);
   }
 
-  async killConnection(connectionID: string) {
+  async killConnection(connectionID?: string) {
+    if (!connectionID) return { results: undefined, error: undefined };
     return this.executeQuery(`KILL CONNECTION ${connectionID};`);
   }
 
diff --git a/backend/recorder/SqlManager.ts b/backend/recorder/SqlManager.ts
index 1a54aaeaabe8cc08f9af34d76f891cb42a4e2618..dd40215fb67bf9b570644dfb4e94b30f7e816e4d 100644
--- a/backend/recorder/SqlManager.ts
+++ b/backend/recorder/SqlManager.ts
@@ -1,5 +1,5 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import { URI, Client, ResultValue } from '@mysql/xdevapi';
+import { Client, ResultValue } from '@mysql/xdevapi';
 import FileIO from '../utils/FileIO';
 import SqlRunner from './SqlRunner';
 import SqlMonitor from './SqlMonitor';
@@ -10,86 +10,143 @@ import DataProcessor from '../data-processor/DataProcessor';
 import SqlAgent from './SqlAgent';
 import { ChartColors } from '../data-processor/types/ChartColors';
 import { ExplainAnalyzeNode } from '../data-processor/types/ExplainAnalyzeNode';
+import { LoginDetails } from '../types/LoginDetails';
 
 export interface RawRecording {
-  result?: { results?: Result; error?: any };
+  result?: { results?: Result; error?: any; columnWidths?: number[] };
   memoryPerformance?: ChartData;
   chartColors?: ChartColors;
+  top3Memory?: string[];
   error?: any;
   optimizerTrace?: ResultValue[][];
   stageTimes?: StageTimes;
   explainAnalyze?: ExplainAnalyzeNode;
+  tabID: string;
 }
 
 export default class SqlManager {
-  connection?: URI;
-
-  database?: string;
-
-  client?: Client;
-
-  runner?: SqlRunner;
-
-  monitor?: SqlMonitor;
-
-  agent?: SqlAgent;
-
-  userQueryIsRunning = false;
-
-  cancel = false;
+  connections: {
+    [key: string]: {
+      loginDetails: LoginDetails;
+      client?: Client;
+      runner: SqlRunner;
+      monitor: SqlMonitor;
+      agent: SqlAgent;
+      running: boolean;
+      cancelled: boolean;
+    };
+  } = {};
 
-  async connect(connection?: URI, database?: string, savePassword?: boolean) {
-    this.connection = connection;
-    this.database = database;
-    this.runner = new SqlRunner();
-    this.client = await this.runner.connect(connection, database);
-    this.monitor = new SqlMonitor();
-    await this.monitor.connect(this.client, database);
-    this.agent = new SqlAgent();
-    await this.agent.connect(this.client, database);
-    await FileIO.saveLoginDetails({ ...connection, database, savePassword });
+  async connect(tabID: string, serial?: number, loginDetails?: LoginDetails) {
+    if (!loginDetails) return;
+    SqlManager.log(JSON.stringify(loginDetails, undefined, 2));
+    if (!this.connections[tabID]) {
+      this.connections[tabID] = {
+        loginDetails,
+        runner: new SqlRunner(),
+        monitor: new SqlMonitor(),
+        agent: new SqlAgent(),
+        running: false,
+        cancelled: false,
+      };
+    }
+    const connection = this.connections[tabID];
+    connection.client = await connection.runner.connect(loginDetails);
+    await connection.monitor.connect(
+      connection.client,
+      loginDetails?.database,
+      serial
+    );
+    await connection.agent.connect(connection.client, loginDetails?.database);
+    await FileIO.saveLoginDetails({
+      ...loginDetails,
+      password: loginDetails.savePassword ? loginDetails.password : '',
+    });
   }
 
   async record(
     query: string,
+    tabID: string,
     setUserQueryIsRunning: (running: boolean) => void,
+    stateCallback: (state: string) => void,
     monitorTimeStep: number,
-    explainAnalyze?: boolean,
-    stateCallback?: (state: string) => void
+    explainAnalyze?: boolean
   ): Promise<RawRecording> {
+    const connection = this.connections[tabID];
+    const { runner, monitor, agent } = connection;
+    console.log(
+      `timestep: ${monitorTimeStep}, 
+      query: ${query}, 
+      tabID: ${tabID}, 
+      runnerCID: ${runner.connectionID},
+      runnerTID: ${runner.threadID},
+      monitorCID: ${monitor.connectionID},
+      monitorTID: ${monitor.threadID}`
+    );
     if (
-      !this.runner ||
-      !this.monitor ||
-      !this.runner.connectionID ||
-      !this.runner.threadID ||
-      !this.monitor.connectionID ||
-      !this.monitor.threadID ||
-      !this.agent
+      !runner.connectionID ||
+      !runner.threadID ||
+      !monitor.connectionID ||
+      !monitor.threadID ||
+      !monitorTimeStep ||
+      query === undefined ||
+      !tabID ||
+      !setUserQueryIsRunning
     ) {
       throw new Error(
         'SqlManager.record: Runner, monitor, agent or connectionID was missing. Maybe you forgot to call connect()?'
       );
     }
-    const stageIgnoreBeforeResult = await this.monitor.getStageIgnoreBeforeResult(
-      this.runner.threadID
+    const stageIgnoreBeforeResult = await monitor.getStageIgnoreBeforeResult(
+      runner.threadID
     );
-    const monitoring = this.monitor.monitorConnection(
-      this.runner.connectionID,
+    const monitoring = monitor.monitorConnection(
+      runner.connectionID,
       stageIgnoreBeforeResult,
       monitorTimeStep
     );
 
-    stateCallback?.call(undefined, 'Waiting for monitor to be ready');
-    await this.agent.waitRunning(this.monitor.threadID);
+    stateCallback('Waiting for monitor to be ready');
+    await agent.waitRunning(monitor.threadID);
 
-    stateCallback?.call(undefined, 'Running query');
+    stateCallback('Running query');
     setUserQueryIsRunning(true);
     const t0 = performance.now();
-    const result = await this.runner.executeQuery(query, explainAnalyze);
+    const result = await runner.executeQuery(query, explainAnalyze);
     const t1 = performance.now();
     setUserQueryIsRunning(false);
+    if (connection.cancelled) {
+      connection.cancelled = false;
+      stateCallback('');
+      return { error: 'cancelled', tabID };
+    }
 
-    stateCallback?.call(undefined, 'Query done');
+    // Handling error, aborting
+    if (result?.error) {
+      const optimizerTrace = await runner.getOptimizerTrace();
+      stateCallback('Error detected, aborting monitoring');
+      await this.reset(tabID, monitor.serial);
+      stateCallback('');
+      return {
+        result: {
+          results: result.results,
+          error: result.error,
+          columnWidths: result.results
+            ? DataProcessor.calculateRowWidths(result.results)
+            : undefined,
+        },
+        error: result.error,
+        explainAnalyze: result.results
+          ? DataProcessor.processExplainAnalyze(result.results)
+          : undefined,
+        optimizerTrace: optimizerTrace.results
+          ? DataProcessor.processOptimizerTrace(optimizerTrace.results)
+          : undefined,
+        tabID,
+      };
+    }
+
+    stateCallback('Query done');
     const queryTime = t1 - t0;
     SqlManager.log(
       `Query took ${queryTime} ms, required at least ${
@@ -97,103 +154,122 @@ export default class SqlManager {
       } ms`
     );
 
-    if (this.cancel) {
-      stateCallback?.call(undefined, 'Cancel detected, reconnecting');
-      await this.connect(this.connection, this.database);
-      this.cancel = false;
-      return { error: 'cancelled' };
-    }
+    if (queryTime < monitorTimeStep * 1000 * 2) {
+      stateCallback('Too short query, aborting monitoring');
+      const optimizerTrace = await runner.getOptimizerTrace();
+      await this.reset(tabID, monitor.serial);
 
-    // Handling error, aborting
-    if (result?.error) {
-      stateCallback?.call(undefined, 'Error detected, aborting monitoring');
-      await this.reset();
-      stateCallback?.call(undefined, '');
+      stateCallback('');
       return {
-        result,
-        error: result.error,
+        result: {
+          results: result.results,
+          error: result.error,
+          columnWidths: result.results
+            ? DataProcessor.calculateRowWidths(result.results)
+            : undefined,
+        },
         explainAnalyze: result.results
           ? DataProcessor.processExplainAnalyze(result.results)
           : undefined,
+        optimizerTrace: optimizerTrace.results
+          ? DataProcessor.processOptimizerTrace(optimizerTrace.results)
+          : undefined,
+        tabID,
       };
     }
-    stateCallback?.call(undefined, 'Getting optimizer trace');
-    const optimizerTrace = await this.runner.getOptimizerTrace();
 
-    stateCallback?.call(
-      undefined,
+    stateCallback('Getting optimizer trace');
+    const optimizerTrace = await runner.getOptimizerTrace();
+
+    stateCallback(
       'Waiting for monitoring to finish (may take up to 5 minutes)'
     );
     await monitoring;
-    stateCallback?.call(undefined, 'Getting memory performance');
-    const memoryPerformance = await this.monitor.dumpData();
-    stateCallback?.call(undefined, 'Getting stage times');
-    const stageTimes = await this.monitor.getStageTimes();
-    stateCallback?.call(undefined, '');
+    stateCallback('Getting memory performance');
+    const memoryPerformance = await monitor.dumpData();
+    stateCallback('Getting stage times');
+    const stageTimes = await monitor.getStageTimes();
+    stateCallback('');
+    const mPerformance = DataProcessor.processMemoryPerformance(
+      memoryPerformance.results
+    );
+    const chartData = DataProcessor.getChartData(mPerformance);
     return {
-      result,
+      result: {
+        results: result.results,
+        error: result.error,
+        columnWidths: result.results
+          ? DataProcessor.calculateRowWidths(result.results)
+          : undefined,
+      },
+      memoryPerformance: chartData,
+      chartColors: DataProcessor.processChartColors(chartData),
+      top3Memory: DataProcessor.processTop3ChartData(mPerformance),
+      error: undefined,
       optimizerTrace: DataProcessor.processOptimizerTrace(
         optimizerTrace.results
       ),
-      memoryPerformance: DataProcessor.processMemoryPerformance(
-        memoryPerformance.results
-      ),
       stageTimes: DataProcessor.processStageTimes(stageTimes.results),
-      chartColors: DataProcessor.processChartColors(
-        DataProcessor.processMemoryPerformance(memoryPerformance.results)
-      ),
-      error: undefined,
-      explainAnalyze: DataProcessor.processExplainAnalyze(result.results),
+      explainAnalyze: result.results
+        ? DataProcessor.processExplainAnalyze(result.results)
+        : undefined,
+      tabID,
     };
   }
 
-  async reset() {
-    await this.disconnect();
-    await this.connect(this.connection, this.database);
+  async reset(tabID: string, serial?: number) {
+    await this.disconnect(tabID);
+    await this.connect(tabID, serial, this.connections[tabID].loginDetails);
   }
 
-  async disconnect() {
-    if (
-      !this.runner ||
-      !this.runner.connectionID ||
-      !this.monitor ||
-      !this.monitor.connectionID ||
-      !this.agent
-    ) {
-      console.error(
-        `SqlManager.disconnect: Runner ${Boolean(
-          this.runner
-        )}, Monitor ${Boolean(this.monitor)}, Agent ${Boolean(this.agent)}`
-      );
-      return;
-    }
+  async disconnect(tabID: string) {
+    const connection = this.connections[tabID];
+    if (!connection) return;
+    const { agent, monitor, runner, client } = connection;
+
     // Kill monitor manually from agent as client.close won't interfere if monitor has called its procedure
-    await this.agent?.killConnection(this.monitor.connectionID);
-    await this.agent?.killConnection(this.runner.connectionID);
-    await closeConnection(this.client);
-    SqlManager.log('disconnect(): Disconnected successfully');
+    try {
+      await agent?.killConnection(monitor?.connectionID);
+      await agent?.killConnection(runner?.connectionID);
+      await closeConnection(client);
+      SqlManager.log('disconnect(): Disconnected successfully');
+    } catch (error) {
+      console.error(error);
+    }
   }
 
-  async cancelQuery() {
-    this.cancel = true;
-    await this.disconnect();
+  async abortQueries(tabID: string) {
+    const connection = this.connections[tabID];
+    const { agent, monitor, runner } = connection;
+    await agent?.killQuery(runner?.connectionID);
+    await agent?.killQuery(monitor?.connectionID);
   }
 
-  async checkThreadStates() {
-    const runnerState = await this.agent?.checkThreadState(
-      this.runner?.threadID || ''
-    );
-    const monitorState = await this.agent?.checkThreadState(
-      this.monitor?.threadID || ''
-    );
+  async cancelRecording(tabID: string) {
+    this.connections[tabID].cancelled = true;
+    await this.abortQueries(tabID);
+  }
+
+  async removeConnection(tabID: string) {
+    await this.disconnect(tabID);
+    delete this.connections[tabID];
+    console.log(`Connection for tab ${tabID} removed`);
+  }
+
+  async checkThreadStates(tabID: string) {
+    const connection = this.connections[tabID];
+    const { agent, monitor, runner } = connection;
+    const runnerState = await agent?.checkThreadState(runner?.threadID || '');
+    const monitorState = await agent?.checkThreadState(monitor?.threadID || '');
     SqlManager.log(
       `Runner: ${runnerState?.results?.values}\nMonitor: ${monitorState?.results?.values}`
     );
   }
 
-  async executeQuery(query: string) {
-    if (!this.runner) return [];
-    const result = await this.runner.executeQuery(query);
+  async executeQuery(query: string, tabID: string) {
+    const connection = this.connections[tabID];
+    const { runner } = connection;
+    const result = await runner.executeQuery(query);
     return result;
   }
 
diff --git a/backend/recorder/SqlMonitor.ts b/backend/recorder/SqlMonitor.ts
index 9065b624516cbca444a5b4be7713ea934287c548..5710a11c604e90e0b01748240386a2c28249ef97 100644
--- a/backend/recorder/SqlMonitor.ts
+++ b/backend/recorder/SqlMonitor.ts
@@ -11,7 +11,9 @@ export default class SqlMonitor {
 
   threadID?: string;
 
-  async connect(client?: Client, database?: string) {
+  serial?: number;
+
+  async connect(client?: Client, database?: string, serial?: number) {
     SqlMonitor.log('Connecting');
     this.session = await createSession(client, database);
     let resultSet = await this.executeQuery(`SELECT connection_id();`);
@@ -20,8 +22,9 @@ export default class SqlMonitor {
       `SELECT thread_id FROM performance_schema.threads WHERE processlist_id=${this.connectionID}`
     );
     this.threadID = String(resultSet?.results?.values[0]);
+    this.serial = serial;
     await this.enableStageLogging();
-    await this.createMonitorProcedure();
+    await this.createMonitorProcedure(serial);
   }
 
   async enableStageLogging() {
@@ -34,11 +37,13 @@ export default class SqlMonitor {
     );
   }
 
-  async createMonitorProcedure() {
-    await this.executeQuery('DROP PROCEDURE IF EXISTS monitor_connection;');
+  async createMonitorProcedure(serial?: number) {
+    await this.executeQuery(
+      `DROP PROCEDURE IF EXISTS monitor_connection${serial};`
+    );
     // Create monitoring procedure
     await this.executeQuery(
-      `CREATE PROCEDURE monitor_connection(
+      `CREATE PROCEDURE monitor_connection${serial}(
         IN conn_id BIGINT UNSIGNED,
         IN stage_ignore_before BIGINT UNSIGNED,
         IN timestep_size FLOAT
@@ -50,8 +55,8 @@ export default class SqlMonitor {
 
         SET thd_id = (SELECT thread_id FROM performance_schema.threads WHERE processlist_id=conn_id);
 
-        DROP TABLE IF EXISTS monitoring_data;
-        CREATE TABLE monitoring_data (
+        DROP TABLE IF EXISTS monitoring_data${serial};
+        CREATE TABLE monitoring_data${serial} (
             TS DATETIME(6),
             THREAD_ID BIGINT UNSIGNED,
             EVENT_NAME VARCHAR(128),
@@ -73,7 +78,7 @@ export default class SqlMonitor {
 
         REPEAT
           SET state = (SELECT PROCESSLIST_COMMAND FROM performance_schema.threads WHERE THREAD_ID=thd_id);
-          INSERT INTO monitoring_data
+          INSERT INTO monitoring_data${serial}
             SELECT
               NOW(6) AS 'TS',
               THREAD_ID,
@@ -95,8 +100,8 @@ export default class SqlMonitor {
 
         SET stage_min_ts = (SELECT MIN(timer_start) FROM performance_schema.events_stages_history_long WHERE THREAD_ID=thd_id AND timer_start > stage_ignore_before);
 
-        DROP TABLE IF EXISTS monitoring_stages;
-        CREATE TABLE monitoring_stages AS
+        DROP TABLE IF EXISTS monitoring_stages${serial};
+        CREATE TABLE monitoring_stages${serial} AS
           SELECT
             EVENT_NAME,
             SOURCE,
@@ -126,27 +131,31 @@ export default class SqlMonitor {
       `Monitoring with timestep of ${timestep} seconds, connectionID ${connectionID}`
     );
     await this.executeQuery(
-      `call monitor_connection(${connectionID}, ${stageIgnoreBeforeResult}, ${timestep});`
+      `call monitor_connection${this.serial}(${connectionID}, ${stageIgnoreBeforeResult}, ${timestep});`
     );
   }
 
   async dumpData() {
     await this.executeQuery(
-      'SET @min_ts = (SELECT UNIX_TIMESTAMP(MIN(TS)) FROM monitoring_data);'
+      `SET @min_ts = (SELECT UNIX_TIMESTAMP(MIN(TS)) FROM monitoring_data${this.serial});`
     );
 
     const data = await this.executeQuery(
-      `SELECT UNIX_TIMESTAMP(TS) - @min_ts, EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED FROM monitoring_data ORDER BY 1;`
+      `SELECT UNIX_TIMESTAMP(TS) - @min_ts, EVENT_NAME, CURRENT_NUMBER_OF_BYTES_USED FROM monitoring_data${this.serial} ORDER BY 1;`
+    );
+    await this.executeQuery(
+      `DROP TABLE IF EXISTS monitoring_data${this.serial};`
     );
-    await this.executeQuery(`DROP TABLE IF EXISTS monitoring_data;`);
     return data;
   }
 
   async getStageTimes() {
     const stageTimesRaw = await this.executeQuery(
-      `select event_name, source, start, end from monitoring_stages;`
+      `select event_name, source, start, end from monitoring_stages${this.serial};`
+    );
+    await this.executeQuery(
+      `DROP TABLE IF EXISTS monitoring_stages${this.serial};`
     );
-    await this.executeQuery(`DROP TABLE IF EXISTS monitoring_stages;`);
     return stageTimesRaw;
   }
 
diff --git a/backend/recorder/SqlRunner.ts b/backend/recorder/SqlRunner.ts
index ccea83e7616c2d46115bc9fb35edc859f6c179ca..60055525596c540c667e5be032af91d361e96bf5 100644
--- a/backend/recorder/SqlRunner.ts
+++ b/backend/recorder/SqlRunner.ts
@@ -1,4 +1,5 @@
-import { URI, Session } from '@mysql/xdevapi';
+import { Session } from '@mysql/xdevapi';
+import { LoginDetails } from '../types/LoginDetails';
 import { createConnection, createSession, runQuery } from '../utils/mysql';
 
 /**
@@ -11,11 +12,11 @@ export default class SqlRunner {
 
   threadID?: string;
 
-  async connect(connection?: URI, database?: string) {
-    const client = await createConnection(connection, {
+  async connect(loginDetails?: LoginDetails) {
+    const client = await createConnection(loginDetails, {
       pooling: { enabled: true },
     });
-    this.session = await createSession(client, database);
+    this.session = await createSession(client, loginDetails?.database);
     let resultSet = await runQuery(`SELECT connection_id();`, this.session);
     this.connectionID = String(resultSet?.results?.values[0]);
     resultSet = await runQuery(
diff --git a/backend/utils/LoginDetails.ts b/backend/types/LoginDetails.ts
similarity index 100%
rename from backend/utils/LoginDetails.ts
rename to backend/types/LoginDetails.ts
diff --git a/backend/utils/FileIO.ts b/backend/utils/FileIO.ts
index 8cc94ed2d50f7f910f384867133f130e2fff20cd..775733be8c95689c81be7ddf1eea33deaa2e5a8c 100644
--- a/backend/utils/FileIO.ts
+++ b/backend/utils/FileIO.ts
@@ -1,23 +1,37 @@
 import * as fs from 'fs';
-import { LoginDetails } from './LoginDetails';
+import path from 'path';
+import process from 'process';
+import { Recording } from '../../app/types/Recording';
+import { LoginDetails } from '../types/LoginDetails';
 
 // fs docs: https://nodejs.org/api/fs.html#fs_file_paths
 
 export default class FileIO {
   static async saveLoginDetails(loginDetails: LoginDetails) {
-    const loginDetailsLocation = 'loginDetails.json';
+    const loginDetailsLocation = path.join(
+      this.getAppDataLocation(),
+      'loginDetails.json'
+    );
     const fixedLoginDetails = loginDetails;
     if (!loginDetails.savePassword) {
       fixedLoginDetails.password = '';
     }
+    if (!fs.existsSync(this.getAppDataLocation())) {
+      console.log('Folder did not exist, creating');
+      await fs.promises.mkdir(this.getAppDataLocation());
+    }
     await fs.promises.writeFile(
       loginDetailsLocation,
       JSON.stringify(fixedLoginDetails, undefined, 2)
     );
+    return true;
   }
 
   static async loadLoginDetails(): Promise<LoginDetails | undefined> {
-    const loginDetailsLocation = 'loginDetails.json';
+    const loginDetailsLocation = path.join(
+      this.getAppDataLocation(),
+      'loginDetails.json'
+    );
     if (fs.existsSync(loginDetailsLocation)) {
       const data = await fs.promises.readFile(loginDetailsLocation);
       return JSON.parse(String(data));
@@ -25,4 +39,64 @@ export default class FileIO {
     console.log(`${loginDetailsLocation} does not exist!`);
     return undefined;
   }
+
+  static async saveRecordingDetails(recDetails: Recording, filename: string) {
+    const dest = path.join(this.getAppDataLocation(), 'Recordings/');
+    const savePath = `${dest + filename}.json`;
+    if (!fs.existsSync(dest)) {
+      fs.mkdirSync(dest);
+    }
+    await fs.promises.writeFile(
+      savePath,
+      JSON.stringify(recDetails, undefined, 2)
+    );
+    return savePath;
+  }
+
+  static async loadRecording(filepath: string): Promise<Recording> {
+    try {
+      if (fs.existsSync(filepath)) {
+        const data = await fs.promises.readFile(filepath);
+        const object = JSON.parse(String(data));
+        const castObject = object as Recording;
+        if (
+          !(
+            castObject.uuid &&
+            castObject.explainAnalyze !== undefined &&
+            castObject.label !== undefined &&
+            castObject.query !== undefined
+          )
+        ) {
+          throw new Error('File was not a recording');
+        }
+        return castObject;
+      }
+      throw new Error(`${filepath} does not exist! `);
+    } catch (error) {
+      throw new Error('Filetype not supported');
+    }
+  }
+
+  static getAppDataLocation() {
+    const { platform } = process;
+    if (platform === 'darwin' && process.env.HOME) {
+      return path.join(
+        process.env.HOME,
+        'Library',
+        'Application Support',
+        'MySQL Query Profiler'
+      );
+    }
+    if (platform === 'win32' && process.env.APPDATA) {
+      return path.join(process.env.APPDATA, 'MySQL Query Profiler');
+    }
+    if (platform === 'linux' && process.env.HOME) {
+      return path.join(process.env.HOME, '.MySQL Query Profiler');
+    }
+
+    console.log(
+      'Unrecognized platform: App data will be written to current location if possible'
+    );
+    return '';
+  }
 }
diff --git a/package.json b/package.json
index 48174a9ebbb95491a876af70944f3d963c9d6107..bc5253ea899b1975d2e4d0e240b5f81f56c88f0d 100644
--- a/package.json
+++ b/package.json
@@ -149,7 +149,7 @@
     "@types/history": "^4.7.6",
     "@types/jest": "^26.0.10",
     "@types/mysql": "^2.15.15",
-    "@types/node": "12",
+    "@types/node": "14.11.8",
     "@types/react": "^16.9.49",
     "@types/react-color": "^3.0.4",
     "@types/react-dom": "^16.9.8",
@@ -175,23 +175,23 @@
     "chalk": "^4.1.0",
     "core-js": "^3.6.5",
     "cross-env": "^7.0.2",
-    "css-loader": "^3.6.0",
+    "css-loader": "^4.3.0",
     "detect-port": "^1.3.0",
     "electron": "^10",
     "electron-builder": "^22.3.6",
     "electron-devtools-installer": "^3.1.1",
-    "electron-rebuild": "^1.10.0",
+    "electron-rebuild": "^2.2.0",
     "enzyme": "^3.11.0",
     "enzyme-adapter-react-16": "^1.15.2",
     "enzyme-to-json": "^3.5.0",
     "eslint": "^7.9.0",
     "eslint-config-airbnb": "^18.2.0",
-    "eslint-config-airbnb-typescript": "^9.0.0",
+    "eslint-config-airbnb-typescript": "^11.0.0",
     "eslint-config-prettier": "^6.11.0",
-    "eslint-import-resolver-webpack": "^0.12.2",
+    "eslint-import-resolver-webpack": "^0.13.0",
     "eslint-plugin-compat": "^3.8.0",
     "eslint-plugin-import": "^2.22.0",
-    "eslint-plugin-jest": "^23.18.0",
+    "eslint-plugin-jest": "^24.1.0",
     "eslint-plugin-jsx-a11y": "6.3.1",
     "eslint-plugin-prettier": "^3.1.4",
     "eslint-plugin-promise": "^4.2.1",
@@ -203,25 +203,25 @@
     "identity-obj-proxy": "^3.0.0",
     "jest": "^26.1.0",
     "lint-staged": "^10.2.11",
-    "mini-css-extract-plugin": "^0.9.0",
+    "mini-css-extract-plugin": "^1.0.0",
     "opencollective-postinstall": "^2.0.3",
     "optimize-css-assets-webpack-plugin": "^5.0.3",
     "prettier": "^2.1.1",
     "react-refresh": "^0.8.3",
     "react-test-renderer": "^16.12.0",
     "rimraf": "^3.0.0",
-    "sass-loader": "^9.0.2",
-    "style-loader": "^1.2.1",
+    "sass-loader": "^10.0.3",
+    "style-loader": "^2.0.0",
     "stylelint": "^13.6.1",
     "stylelint-config-prettier": "^8.0.2",
     "stylelint-config-standard": "^20.0.0",
-    "terser-webpack-plugin": "^3.0.7",
+    "terser-webpack-plugin": "^4.2.3",
     "testcafe": "^1.8.8",
     "testcafe-browser-provider-electron": "^0.0.15",
     "testcafe-react-selectors": "^4.0.0",
     "ts-node": "^9.0.0",
     "type-fest": "^0.17.0",
-    "typescript": "^3.9.7",
+    "typescript": "^4.0.3",
     "typings-for-css-modules-loader": "^1.7.0",
     "url-loader": "^4.1.0",
     "webpack": "^4.44.2",
@@ -241,13 +241,13 @@
     "electron-log": "^4.2.4",
     "electron-react-devtools": "^0.5.3",
     "electron-updater": "^4.3.5",
-    "history": "^4.7.2",
+    "history": "^5.0.0",
     "lodash": "^4.17.20",
     "react": "^16.13.1",
+    "react-color": "^2.18.1",
     "react-dom": "^16.12.0",
     "react-flame-graph": "^1.4.0",
     "react-hot-loader": "^4.12.21",
-    "react-color": "^2.18.1",
     "react-json-view": "^1.19.1",
     "react-panelgroup": "^1.0.12",
     "react-router-dom": "^5.2.0",
@@ -257,7 +257,8 @@
     "regenerator-runtime": "^0.13.7",
     "source-map-support": "^0.5.19",
     "use-deep-compare-effect": "^1.4.0",
-    "uuid": "^8.3.0"
+    "uuid": "^8.3.0",
+    "victory": "^35.3.0"
   },
   "devEngines": {
     "node": ">=7.x",
diff --git a/resources/Icon.icns b/resources/Icon.icns
new file mode 100644
index 0000000000000000000000000000000000000000..86f02e6756137dbb1750d6a583c45dc9215bd397
Binary files /dev/null and b/resources/Icon.icns differ
diff --git a/resources/icon.ico b/resources/icon.ico
index 15e5d78bb470d7236df9db38259c730560d1141d..48c7421ab68bf5359f6ab071a3715081eee2d6e2 100644
Binary files a/resources/icon.ico and b/resources/icon.ico differ
diff --git a/resources/icon.png b/resources/icon.png
index 2c0f60050d903da7e723646396a896be9b90a9a8..64240740d32f4e15777fdc9c207a68504bf32237 100644
Binary files a/resources/icon.png and b/resources/icon.png differ
diff --git a/resources/icons/1024x1024.png b/resources/icons/1024x1024.png
new file mode 100644
index 0000000000000000000000000000000000000000..64240740d32f4e15777fdc9c207a68504bf32237
Binary files /dev/null and b/resources/icons/1024x1024.png differ
diff --git a/resources/icons/128x128.png b/resources/icons/128x128.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d7137fa50f0b3917d14be6f4ddd77ac67bccda8
Binary files /dev/null and b/resources/icons/128x128.png differ
diff --git a/resources/icons/16x16.png b/resources/icons/16x16.png
index b2a9c915f2c8059368c8e6a4c14168756e886745..15dad1020732bde31b69dc2c7922b2203b73d104 100644
Binary files a/resources/icons/16x16.png and b/resources/icons/16x16.png differ
diff --git a/resources/icons/24x24.png b/resources/icons/24x24.png
new file mode 100644
index 0000000000000000000000000000000000000000..f31ebcdbde7d58f5057133886a433c1ce41b10c2
Binary files /dev/null and b/resources/icons/24x24.png differ
diff --git a/resources/icons/256x256.png b/resources/icons/256x256.png
new file mode 100644
index 0000000000000000000000000000000000000000..5a633f2461d3b1328c3589c5580b311f64d88e0d
Binary files /dev/null and b/resources/icons/256x256.png differ
diff --git a/resources/icons/32x32.png b/resources/icons/32x32.png
index 2fca10cdbc0186242fbec8f6edbbdad44bc64f0f..bf32050f3149033e264030678da808f2d8ad3742 100644
Binary files a/resources/icons/32x32.png and b/resources/icons/32x32.png differ
diff --git a/resources/icons/48x48.png b/resources/icons/48x48.png
index 581a1367d311e6b6cdffe536b6f3936c1f956230..b60e058601303accab200d1b177695d4ac5fde0e 100644
Binary files a/resources/icons/48x48.png and b/resources/icons/48x48.png differ
diff --git a/resources/icons/512x512.png b/resources/icons/512x512.png
index 6ff3ba1ecab12c8d4dbfb34dc88b3c6a7350ca27..60d4fba78f5f8696c3982fa6a88431c225f33a00 100644
Binary files a/resources/icons/512x512.png and b/resources/icons/512x512.png differ
diff --git a/resources/icons/64x64.png b/resources/icons/64x64.png
new file mode 100644
index 0000000000000000000000000000000000000000..cb71ab748a6971a625e3a72313ce391fb1e3e4e8
Binary files /dev/null and b/resources/icons/64x64.png differ
diff --git a/resources/icons/96x96.png b/resources/icons/96x96.png
index ecc2b8a558bf6dd2089bf90cc5c92ece116cd4ed..cd7ad02fe05128cb76395428cd2c968f635f6ddb 100644
Binary files a/resources/icons/96x96.png and b/resources/icons/96x96.png differ
diff --git a/yarn.lock b/yarn.lock
index 8d6bf039776667ca71bdb72e180a7a7e851633a8..4dc5431d2b0569ea63800471fe07c0e8b6662a46 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -42,7 +42,7 @@
     invariant "^2.2.4"
     semver "^5.5.0"
 
-"@babel/core@>=7.9.0", "@babel/core@^7.11.1":
+"@babel/core@>=7.9.0", "@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.7.5":
   version "7.11.6"
   resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.6.tgz#3a9455dc7387ff1bac45770650bc13ba04a15651"
   integrity sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==
@@ -64,29 +64,7 @@
     semver "^5.4.1"
     source-map "^0.5.0"
 
-"@babel/core@^7.1.0", "@babel/core@^7.7.5":
-  version "7.11.4"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.4.tgz#4301dfdfafa01eeb97f1896c5501a3f0655d4229"
-  integrity sha512-5deljj5HlqRXN+5oJTY7Zs37iH3z3b++KjiKtIsJy1NrjOOVSEaJHEetLBhyu0aQOSNNZ/0IuEAan9GzRuDXHg==
-  dependencies:
-    "@babel/code-frame" "^7.10.4"
-    "@babel/generator" "^7.11.4"
-    "@babel/helper-module-transforms" "^7.11.0"
-    "@babel/helpers" "^7.10.4"
-    "@babel/parser" "^7.11.4"
-    "@babel/template" "^7.10.4"
-    "@babel/traverse" "^7.11.0"
-    "@babel/types" "^7.11.0"
-    convert-source-map "^1.7.0"
-    debug "^4.1.0"
-    gensync "^1.0.0-beta.1"
-    json5 "^2.1.2"
-    lodash "^4.17.19"
-    resolve "^1.3.2"
-    semver "^5.4.1"
-    source-map "^0.5.0"
-
-"@babel/generator@^7.11.0", "@babel/generator@^7.11.4", "@babel/generator@^7.11.5", "@babel/generator@^7.11.6":
+"@babel/generator@^7.11.0", "@babel/generator@^7.11.5", "@babel/generator@^7.11.6":
   version "7.11.6"
   resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620"
   integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==
@@ -319,16 +297,16 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.1.0", "@babel/parser@^7.7.0":
-  version "7.11.4"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.4.tgz#6fa1a118b8b0d80d0267b719213dc947e88cc0ca"
-  integrity sha512-MggwidiH+E9j5Sh8pbrX5sJvMcsqS5o+7iB42M9/k0CD63MjYbdP4nhSh7uB5wnv2/RVzTZFTxzF/kIa5mrCqA==
-
-"@babel/parser@^7.10.4", "@babel/parser@^7.11.0", "@babel/parser@^7.11.4", "@babel/parser@^7.11.5":
+"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.0", "@babel/parser@^7.11.5":
   version "7.11.5"
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037"
   integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==
 
+"@babel/parser@^7.7.0":
+  version "7.11.4"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.4.tgz#6fa1a118b8b0d80d0267b719213dc947e88cc0ca"
+  integrity sha512-MggwidiH+E9j5Sh8pbrX5sJvMcsqS5o+7iB42M9/k0CD63MjYbdP4nhSh7uB5wnv2/RVzTZFTxzF/kIa5mrCqA==
+
 "@babel/plugin-proposal-async-generator-functions@^7.10.4":
   version "7.10.5"
   resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558"
@@ -1105,7 +1083,7 @@
     core-js-pure "^3.0.0"
     regenerator-runtime "^0.13.4"
 
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@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.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
   version "7.11.2"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
   integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
@@ -1121,22 +1099,7 @@
     "@babel/parser" "^7.10.4"
     "@babel/types" "^7.10.4"
 
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.7.0":
-  version "7.11.0"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.0.tgz#9b996ce1b98f53f7c3e4175115605d56ed07dd24"
-  integrity sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==
-  dependencies:
-    "@babel/code-frame" "^7.10.4"
-    "@babel/generator" "^7.11.0"
-    "@babel/helper-function-name" "^7.10.4"
-    "@babel/helper-split-export-declaration" "^7.11.0"
-    "@babel/parser" "^7.11.0"
-    "@babel/types" "^7.11.0"
-    debug "^4.1.0"
-    globals "^11.1.0"
-    lodash "^4.17.19"
-
-"@babel/traverse@^7.10.4", "@babel/traverse@^7.11.0", "@babel/traverse@^7.11.5":
+"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5":
   version "7.11.5"
   resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3"
   integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==
@@ -1151,16 +1114,22 @@
     globals "^11.1.0"
     lodash "^4.17.19"
 
-"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.7.0":
+"@babel/traverse@^7.7.0":
   version "7.11.0"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d"
-  integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.0.tgz#9b996ce1b98f53f7c3e4175115605d56ed07dd24"
+  integrity sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==
   dependencies:
-    "@babel/helper-validator-identifier" "^7.10.4"
+    "@babel/code-frame" "^7.10.4"
+    "@babel/generator" "^7.11.0"
+    "@babel/helper-function-name" "^7.10.4"
+    "@babel/helper-split-export-declaration" "^7.11.0"
+    "@babel/parser" "^7.11.0"
+    "@babel/types" "^7.11.0"
+    debug "^4.1.0"
+    globals "^11.1.0"
     lodash "^4.17.19"
-    to-fast-properties "^2.0.0"
 
-"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.4.4":
+"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
   version "7.11.5"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d"
   integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==
@@ -1169,6 +1138,15 @@
     lodash "^4.17.19"
     to-fast-properties "^2.0.0"
 
+"@babel/types@^7.7.0":
+  version "7.11.0"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d"
+  integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==
+  dependencies:
+    "@babel/helper-validator-identifier" "^7.10.4"
+    lodash "^4.17.19"
+    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"
@@ -1258,93 +1236,93 @@
   resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
   integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
 
-"@jest/console@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.3.0.tgz#ed04063efb280c88ba87388b6f16427c0a85c856"
-  integrity sha512-/5Pn6sJev0nPUcAdpJHMVIsA8sKizL2ZkcKPE5+dJrCccks7tcM7c9wbgHudBJbxXLoTbqsHkG1Dofoem4F09w==
+"@jest/console@^26.5.2":
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.5.2.tgz#94fc4865b1abed7c352b5e21e6c57be4b95604a6"
+  integrity sha512-lJELzKINpF1v74DXHbCRIkQ/+nUV1M+ntj+X1J8LxCgpmJZjfLmhFejiMSbjjD66fayxl5Z06tbs3HMyuik6rw==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     "@types/node" "*"
     chalk "^4.0.0"
-    jest-message-util "^26.3.0"
-    jest-util "^26.3.0"
+    jest-message-util "^26.5.2"
+    jest-util "^26.5.2"
     slash "^3.0.0"
 
-"@jest/core@^26.4.2":
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.4.2.tgz#85d0894f31ac29b5bab07aa86806d03dd3d33edc"
-  integrity sha512-sDva7YkeNprxJfepOctzS8cAk9TOekldh+5FhVuXS40+94SHbiicRO1VV2tSoRtgIo+POs/Cdyf8p76vPTd6dg==
+"@jest/core@^26.5.2":
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.5.2.tgz#e39f14676f4ba4632ecabfdc374071ab22131f22"
+  integrity sha512-LLTo1LQMg7eJjG/+P1NYqFof2B25EV1EqzD5FonklihG4UJKiK2JBIvWonunws6W7e+DhNLoFD+g05tCY03eyA==
   dependencies:
-    "@jest/console" "^26.3.0"
-    "@jest/reporters" "^26.4.1"
-    "@jest/test-result" "^26.3.0"
-    "@jest/transform" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/console" "^26.5.2"
+    "@jest/reporters" "^26.5.2"
+    "@jest/test-result" "^26.5.2"
+    "@jest/transform" "^26.5.2"
+    "@jest/types" "^26.5.2"
     "@types/node" "*"
     ansi-escapes "^4.2.1"
     chalk "^4.0.0"
     exit "^0.1.2"
     graceful-fs "^4.2.4"
-    jest-changed-files "^26.3.0"
-    jest-config "^26.4.2"
-    jest-haste-map "^26.3.0"
-    jest-message-util "^26.3.0"
+    jest-changed-files "^26.5.2"
+    jest-config "^26.5.2"
+    jest-haste-map "^26.5.2"
+    jest-message-util "^26.5.2"
     jest-regex-util "^26.0.0"
-    jest-resolve "^26.4.0"
-    jest-resolve-dependencies "^26.4.2"
-    jest-runner "^26.4.2"
-    jest-runtime "^26.4.2"
-    jest-snapshot "^26.4.2"
-    jest-util "^26.3.0"
-    jest-validate "^26.4.2"
-    jest-watcher "^26.3.0"
+    jest-resolve "^26.5.2"
+    jest-resolve-dependencies "^26.5.2"
+    jest-runner "^26.5.2"
+    jest-runtime "^26.5.2"
+    jest-snapshot "^26.5.2"
+    jest-util "^26.5.2"
+    jest-validate "^26.5.2"
+    jest-watcher "^26.5.2"
     micromatch "^4.0.2"
     p-each-series "^2.1.0"
     rimraf "^3.0.0"
     slash "^3.0.0"
     strip-ansi "^6.0.0"
 
-"@jest/environment@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.3.0.tgz#e6953ab711ae3e44754a025f838bde1a7fd236a0"
-  integrity sha512-EW+MFEo0DGHahf83RAaiqQx688qpXgl99wdb8Fy67ybyzHwR1a58LHcO376xQJHfmoXTu89M09dH3J509cx2AA==
+"@jest/environment@^26.5.2":
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.5.2.tgz#eba3cfc698f6e03739628f699c28e8a07f5e65fe"
+  integrity sha512-YjhCD/Zhkz0/1vdlS/QN6QmuUdDkpgBdK4SdiVg4Y19e29g4VQYN5Xg8+YuHjdoWGY7wJHMxc79uDTeTOy9Ngw==
   dependencies:
-    "@jest/fake-timers" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/fake-timers" "^26.5.2"
+    "@jest/types" "^26.5.2"
     "@types/node" "*"
-    jest-mock "^26.3.0"
+    jest-mock "^26.5.2"
 
-"@jest/fake-timers@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.3.0.tgz#f515d4667a6770f60ae06ae050f4e001126c666a"
-  integrity sha512-ZL9ytUiRwVP8ujfRepffokBvD2KbxbqMhrXSBhSdAhISCw3gOkuntisiSFv+A6HN0n0fF4cxzICEKZENLmW+1A==
+"@jest/fake-timers@^26.5.2":
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.5.2.tgz#1291ac81680ceb0dc7daa1f92c059307eea6400a"
+  integrity sha512-09Hn5Oraqt36V1akxQeWMVL0fR9c6PnEhpgLaYvREXZJAh2H2Y+QLCsl0g7uMoJeoWJAuz4tozk1prbR1Fc1sw==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     "@sinonjs/fake-timers" "^6.0.1"
     "@types/node" "*"
-    jest-message-util "^26.3.0"
-    jest-mock "^26.3.0"
-    jest-util "^26.3.0"
+    jest-message-util "^26.5.2"
+    jest-mock "^26.5.2"
+    jest-util "^26.5.2"
 
-"@jest/globals@^26.4.2":
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.4.2.tgz#73c2a862ac691d998889a241beb3dc9cada40d4a"
-  integrity sha512-Ot5ouAlehhHLRhc+sDz2/9bmNv9p5ZWZ9LE1pXGGTCXBasmi5jnYjlgYcYt03FBwLmZXCZ7GrL29c33/XRQiow==
+"@jest/globals@^26.5.2":
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.5.2.tgz#c333f82c29e19ecb609a75d1a532915a5c956c59"
+  integrity sha512-9PmnFsAUJxpPt1s/stq02acS1YHliVBDNfAWMe1bwdRr1iTCfhbNt3ERQXrO/ZfZSweftoA26Q/2yhSVSWQ3sw==
   dependencies:
-    "@jest/environment" "^26.3.0"
-    "@jest/types" "^26.3.0"
-    expect "^26.4.2"
+    "@jest/environment" "^26.5.2"
+    "@jest/types" "^26.5.2"
+    expect "^26.5.2"
 
-"@jest/reporters@^26.4.1":
-  version "26.4.1"
-  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.4.1.tgz#3b4d6faf28650f3965f8b97bc3d114077fb71795"
-  integrity sha512-aROTkCLU8++yiRGVxLsuDmZsQEKO6LprlrxtAuzvtpbIFl3eIjgIf3EUxDKgomkS25R9ZzwGEdB5weCcBZlrpQ==
+"@jest/reporters@^26.5.2":
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.5.2.tgz#0f1c900c6af712b46853d9d486c9c0382e4050f6"
+  integrity sha512-zvq6Wvy6MmJq/0QY0YfOPb49CXKSf42wkJbrBPkeypVa8I+XDxijvFuywo6TJBX/ILPrdrlE/FW9vJZh6Rf9vA==
   dependencies:
     "@bcoe/v8-coverage" "^0.2.3"
-    "@jest/console" "^26.3.0"
-    "@jest/test-result" "^26.3.0"
-    "@jest/transform" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/console" "^26.5.2"
+    "@jest/test-result" "^26.5.2"
+    "@jest/transform" "^26.5.2"
+    "@jest/types" "^26.5.2"
     chalk "^4.0.0"
     collect-v8-coverage "^1.0.0"
     exit "^0.1.2"
@@ -1355,10 +1333,10 @@
     istanbul-lib-report "^3.0.0"
     istanbul-lib-source-maps "^4.0.0"
     istanbul-reports "^3.0.2"
-    jest-haste-map "^26.3.0"
-    jest-resolve "^26.4.0"
-    jest-util "^26.3.0"
-    jest-worker "^26.3.0"
+    jest-haste-map "^26.5.2"
+    jest-resolve "^26.5.2"
+    jest-util "^26.5.2"
+    jest-worker "^26.5.0"
     slash "^3.0.0"
     source-map "^0.6.0"
     string-length "^4.0.1"
@@ -1367,51 +1345,51 @@
   optionalDependencies:
     node-notifier "^8.0.0"
 
-"@jest/source-map@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.3.0.tgz#0e646e519883c14c551f7b5ae4ff5f1bfe4fc3d9"
-  integrity sha512-hWX5IHmMDWe1kyrKl7IhFwqOuAreIwHhbe44+XH2ZRHjrKIh0LO5eLQ/vxHFeAfRwJapmxuqlGAEYLadDq6ZGQ==
+"@jest/source-map@^26.5.0":
+  version "26.5.0"
+  resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.5.0.tgz#98792457c85bdd902365cd2847b58fff05d96367"
+  integrity sha512-jWAw9ZwYHJMe9eZq/WrsHlwF8E3hM9gynlcDpOyCb9bR8wEd9ZNBZCi7/jZyzHxC7t3thZ10gO2IDhu0bPKS5g==
   dependencies:
     callsites "^3.0.0"
     graceful-fs "^4.2.4"
     source-map "^0.6.0"
 
-"@jest/test-result@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.3.0.tgz#46cde01fa10c0aaeb7431bf71e4a20d885bc7fdb"
-  integrity sha512-a8rbLqzW/q7HWheFVMtghXV79Xk+GWwOK1FrtimpI5n1la2SY0qHri3/b0/1F0Ve0/yJmV8pEhxDfVwiUBGtgg==
+"@jest/test-result@^26.5.2":
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.5.2.tgz#cc1a44cfd4db2ecee3fb0bc4e9fe087aa54b5230"
+  integrity sha512-E/Zp6LURJEGSCWpoMGmCFuuEI1OWuI3hmZwmULV0GsgJBh7u0rwqioxhRU95euUuviqBDN8ruX/vP/4bwYolXw==
   dependencies:
-    "@jest/console" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/console" "^26.5.2"
+    "@jest/types" "^26.5.2"
     "@types/istanbul-lib-coverage" "^2.0.0"
     collect-v8-coverage "^1.0.0"
 
-"@jest/test-sequencer@^26.4.2":
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.4.2.tgz#58a3760a61eec758a2ce6080201424580d97cbba"
-  integrity sha512-83DRD8N3M0tOhz9h0bn6Kl6dSp+US6DazuVF8J9m21WAp5x7CqSMaNycMP0aemC/SH/pDQQddbsfHRTBXVUgog==
+"@jest/test-sequencer@^26.5.2":
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.5.2.tgz#c4559c7e134b27b020317303ee5399bf62917a4b"
+  integrity sha512-XmGEh7hh07H2B8mHLFCIgr7gA5Y6Hw1ZATIsbz2fOhpnQ5AnQtZk0gmP0Q5/+mVB2xygO64tVFQxOajzoptkNA==
   dependencies:
-    "@jest/test-result" "^26.3.0"
+    "@jest/test-result" "^26.5.2"
     graceful-fs "^4.2.4"
-    jest-haste-map "^26.3.0"
-    jest-runner "^26.4.2"
-    jest-runtime "^26.4.2"
+    jest-haste-map "^26.5.2"
+    jest-runner "^26.5.2"
+    jest-runtime "^26.5.2"
 
-"@jest/transform@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.3.0.tgz#c393e0e01459da8a8bfc6d2a7c2ece1a13e8ba55"
-  integrity sha512-Isj6NB68QorGoFWvcOjlUhpkT56PqNIsXKR7XfvoDlCANn/IANlh8DrKAA2l2JKC3yWSMH5wS0GwuQM20w3b2A==
+"@jest/transform@^26.5.2":
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.5.2.tgz#6a0033a1d24316a1c75184d010d864f2c681bef5"
+  integrity sha512-AUNjvexh+APhhmS8S+KboPz+D3pCxPvEAGduffaAJYxIFxGi/ytZQkrqcKDUU0ERBAo5R7087fyOYr2oms1seg==
   dependencies:
     "@babel/core" "^7.1.0"
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     babel-plugin-istanbul "^6.0.0"
     chalk "^4.0.0"
     convert-source-map "^1.4.0"
     fast-json-stable-stringify "^2.0.0"
     graceful-fs "^4.2.4"
-    jest-haste-map "^26.3.0"
+    jest-haste-map "^26.5.2"
     jest-regex-util "^26.0.0"
-    jest-util "^26.3.0"
+    jest-util "^26.5.2"
     micromatch "^4.0.2"
     pirates "^4.0.1"
     slash "^3.0.0"
@@ -1428,10 +1406,10 @@
     "@types/yargs" "^15.0.0"
     chalk "^3.0.0"
 
-"@jest/types@^26.3.0":
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.3.0.tgz#97627bf4bdb72c55346eef98e3b3f7ddc4941f71"
-  integrity sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==
+"@jest/types@^26.5.2":
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.5.2.tgz#44c24f30c8ee6c7f492ead9ec3f3c62a5289756d"
+  integrity sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==
   dependencies:
     "@types/istanbul-lib-coverage" "^2.0.0"
     "@types/istanbul-reports" "^3.0.0"
@@ -1439,6 +1417,13 @@
     "@types/yargs" "^15.0.0"
     chalk "^4.0.0"
 
+"@malept/cross-spawn-promise@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.0.tgz#258fde4098f5004a56db67c35f33033af64810f6"
+  integrity sha512-GeIK5rfU1Yd7BZJQPTGZMMmcZy5nhRToPXZcjaDwQDRSewdhp648GT2E4dh+L7+Io7AOW6WQ+GR44QSzja4qxg==
+  dependencies:
+    cross-spawn "^7.0.1"
+
 "@material-ui/core@^4.11.0":
   version "4.11.0"
   resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.11.0.tgz#b69b26e4553c9e53f2bfaf1053e216a0af9be15a"
@@ -1587,6 +1572,11 @@
   resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
   integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
 
+"@sindresorhus/is@^3.1.1":
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-3.1.2.tgz#548650de521b344e3781fbdb0ece4aa6f729afb8"
+  integrity sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ==
+
 "@sinonjs/commons@^1.7.0":
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
@@ -1623,15 +1613,22 @@
   dependencies:
     defer-to-connect "^1.0.1"
 
+"@szmarczak/http-timer@^4.0.5":
+  version "4.0.5"
+  resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152"
+  integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==
+  dependencies:
+    defer-to-connect "^2.0.0"
+
 "@types/anymatch@*":
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
   integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==
 
 "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
-  version "7.1.9"
-  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d"
-  integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==
+  version "7.1.10"
+  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.10.tgz#ca58fc195dd9734e77e57c6f2df565623636ab40"
+  integrity sha512-x8OM8XzITIMyiwl5Vmo2B1cR1S1Ipkyv4mdlbJjMa1lmuKvKY9FrBbEANIaMlnWn5Rf7uO+rC/VgYabNkE17Hw==
   dependencies:
     "@babel/parser" "^7.1.0"
     "@babel/types" "^7.0.0"
@@ -1640,50 +1637,55 @@
     "@types/babel__traverse" "*"
 
 "@types/babel__generator@*":
-  version "7.6.1"
-  resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04"
-  integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==
+  version "7.6.2"
+  resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8"
+  integrity sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==
   dependencies:
     "@babel/types" "^7.0.0"
 
 "@types/babel__template@*":
-  version "7.0.2"
-  resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307"
-  integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.3.tgz#b8aaeba0a45caca7b56a5de9459872dde3727214"
+  integrity sha512-uCoznIPDmnickEi6D0v11SBpW0OuVqHJCa7syXqQHy5uktSCreIlt0iglsCnmvz8yCb38hGcWeseA8cWJSwv5Q==
   dependencies:
     "@babel/parser" "^7.1.0"
     "@babel/types" "^7.0.0"
 
-"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6":
-  version "7.0.13"
-  resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.13.tgz#1874914be974a492e1b4cb00585cabb274e8ba18"
-  integrity sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==
+"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6":
+  version "7.0.15"
+  resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.15.tgz#db9e4238931eb69ef8aab0ad6523d4d4caa39d03"
+  integrity sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A==
   dependencies:
     "@babel/types" "^7.3.0"
 
-"@types/cheerio@*":
-  version "0.22.21"
-  resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.21.tgz#5e37887de309ba11b2e19a6e14cad7874b31a8a3"
-  integrity sha512-aGI3DfswwqgKPiEOTaiHV2ZPC9KEhprpgEbJnv0fZl3SGX0cGgEva1126dGrMC6AJM6v/aihlUgJn9M5DbDZ/Q==
+"@types/cacheable-request@^6.0.1":
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976"
+  integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==
   dependencies:
+    "@types/http-cache-semantics" "*"
+    "@types/keyv" "*"
     "@types/node" "*"
+    "@types/responselike" "*"
 
-"@types/color-name@^1.1.1":
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
-  integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
+"@types/cheerio@*", "@types/cheerio@^0.22.22":
+  version "0.22.22"
+  resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.22.tgz#ae71cf4ca59b8bbaf34c99af7a5d6c8894988f5f"
+  integrity sha512-05DYX4zU96IBfZFY+t3Mh88nlwSMtmmzSYaQkKN48T495VV1dkHSah6qYyDTN5ngaS0i0VonH37m+RuzSM0YiA==
+  dependencies:
+    "@types/node" "*"
 
-"@types/d3-path@*":
-  version "1.0.8"
-  resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.8.tgz#48e6945a8ff43ee0a1ce85c8cfa2337de85c7c79"
-  integrity sha512-AZGHWslq/oApTAHu9+yH/Bnk63y9oFOMROtqPAtxl5uB6qm1x2lueWdVEjsjjV3Qc2+QfuzKIwIR5MvVBakfzA==
+"@types/d3-path@^1":
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.9.tgz#73526b150d14cd96e701597cbf346cfd1fd4a58c"
+  integrity sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==
 
-"@types/d3-shape@*":
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.2.tgz#a41d9d6b10d02e221696b240caf0b5d0f5a588ec"
-  integrity sha512-LtD8EaNYCaBRzHzaAiIPrfcL3DdIysc81dkGlQvv7WQP3+YXV7b0JJTtR1U3bzeRieS603KF4wUo+ZkJVenh8w==
+"@types/d3-shape@^1":
+  version "1.3.4"
+  resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.4.tgz#5a6d8c3026ba8e8a1a985bda8da40acfc9b7b079"
+  integrity sha512-fxmOjs+UqNQGpztD5BOo+KriE0jLFrBP4Ct++0QExv/xfDOT1cpcMxgsZ+5qPmnR0t+GjbwAe1Um1PHpv3G4oA==
   dependencies:
-    "@types/d3-path" "*"
+    "@types/d3-path" "^1"
 
 "@types/debug@^4.1.5":
   version "4.1.5"
@@ -1711,9 +1713,9 @@
     "@types/react" "*"
 
 "@types/enzyme@^3.10.5":
-  version "3.10.6"
-  resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.10.6.tgz#a34a6b09ff732be29c194dc8d786555f4717fd85"
-  integrity sha512-Jxyn2U+UfhmrE7EaeZYfV1sPA5OEDuZmg9Lxj8TxOnfCn71flf0Cn1136LWdCVYj7yapLFmUWqKCcgplWNZCJg==
+  version "3.10.7"
+  resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.10.7.tgz#ebdf3b972d293095e09af479e36c772025285e3a"
+  integrity sha512-J+0wduPGAkzOvW7sr6hshGv1gBI3WXLRTczkRKzVPxLP3xAkYxZmvvagSBPw8Z452fZ8TGUxCmAXcb44yLQksw==
   dependencies:
     "@types/cheerio" "*"
     "@types/react" "*"
@@ -1723,11 +1725,6 @@
   resolved "https://registry.yarnpkg.com/@types/error-stack-parser/-/error-stack-parser-1.3.18.tgz#e01c9f8c85ca83b610320c62258b0c9026ade0f7"
   integrity sha1-4ByfjIXKg7YQMgxiJYsMkCat4Pc=
 
-"@types/eslint-visitor-keys@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
-  integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
-
 "@types/estree@^0.0.39":
   version "0.0.39"
   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
@@ -1755,16 +1752,16 @@
   dependencies:
     "@types/node" "*"
 
-"@types/history@*":
-  version "4.7.7"
-  resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.7.tgz#613957d900fab9ff84c8dfb24fa3eef0c2a40896"
-  integrity sha512-2xtoL22/3Mv6a70i4+4RB7VgbDDORoWwjcqeNysojZA0R7NK17RbY5Gof/2QiFfJgX+KkWghbwJ+d/2SB8Ndzg==
-
-"@types/history@^4.7.6":
+"@types/history@*", "@types/history@^4.7.6":
   version "4.7.8"
   resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
   integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
 
+"@types/http-cache-semantics@*":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a"
+  integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==
+
 "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@@ -1800,7 +1797,7 @@
     jest-diff "^25.2.1"
     pretty-format "^25.2.1"
 
-"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5":
+"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6":
   version "7.0.6"
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
   integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
@@ -1810,6 +1807,13 @@
   resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
   integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
 
+"@types/keyv@*":
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
+  integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==
+  dependencies:
+    "@types/node" "*"
+
 "@types/lodash@^4.14.72":
   version "4.14.161"
   resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18"
@@ -1832,25 +1836,20 @@
   dependencies:
     "@types/node" "*"
 
-"@types/node@*":
-  version "14.11.1"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.1.tgz#56af902ad157e763f9ba63d671c39cda3193c835"
-  integrity sha512-oTQgnd0hblfLsJ6BvJzzSL+Inogp3lq9fGgqRkMB/ziKMgEUaFl801OncOzUmalfzt14N0oPHMK47ipl+wbTIw==
-
-"@types/node@12":
-  version "12.12.62"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.62.tgz#733923d73669188d35950253dd18a21570085d2b"
-  integrity sha512-qAfo81CsD7yQIM9mVyh6B/U47li5g7cfpVQEDMfQeF8pSZVwzbhwU3crc0qG4DmpsebpJPR49AKOExQyJ05Cpg==
+"@types/node@*", "@types/node@14.11.8":
+  version "14.11.8"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.8.tgz#fe2012f2355e4ce08bca44aeb3abbb21cf88d33f"
+  integrity sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==
 
 "@types/node@^10.12.19":
-  version "10.17.35"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.35.tgz#58058f29b870e6ae57b20e4f6e928f02b7129f56"
-  integrity sha512-gXx7jAWpMddu0f7a+L+txMplp3FnHl53OhQIF9puXKq3hDGY/GjH+MF04oWnV/adPSCrbtHumDCFwzq2VhltWA==
+  version "10.17.39"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.39.tgz#ce1122758d0608de8303667cebf171f44192629b"
+  integrity sha512-dJLCxrpQmgyxYGcl0Ae9MTsQgI22qHHcGFj/8VKu7McJA5zQpnuGjoksnxbo1JxSjW/Nahnl13W8MYZf01CZHA==
 
 "@types/node@^12.0.12":
-  version "12.12.54"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.54.tgz#a4b58d8df3a4677b6c08bfbc94b7ad7a7a5f82d1"
-  integrity sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w==
+  version "12.12.67"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.67.tgz#4f86badb292e822e3b13730a1f9713ed2377f789"
+  integrity sha512-R48tgL2izApf+9rYNH+3RBMbRpPeW3N8f0I9HMhggeq4UXwBDqumJ14SDs4ctTMhG11pIOduZ4z3QWGOiMc9Vg==
 
 "@types/normalize-package-data@^2.4.0":
   version "2.4.0"
@@ -1863,9 +1862,9 @@
   integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
 
 "@types/prettier@^2.0.0":
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.2.tgz#5bb52ee68d0f8efa9cc0099920e56be6cc4e37f3"
-  integrity sha512-IkVfat549ggtkZUthUzEX49562eGikhSYeVGX97SkMFn+sTZrgRewXjQ4tPKFPCykZHkX1Zfd9OoELGqKU2jJA==
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.1.tgz#be148756d5480a84cde100324c03a86ae5739fb5"
+  integrity sha512-2zs+O+UkDsJ1Vcp667pd3f8xearMdopz/z54i99wtRDI5KLmngk7vlrYZD0ZjKHaROR03EznlBbVY9PfAEyJIQ==
 
 "@types/prop-types@*":
   version "15.7.3"
@@ -1900,9 +1899,9 @@
     "@types/react" "*"
 
 "@types/react-router-dom@^5.1.5":
-  version "5.1.5"
-  resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090"
-  integrity sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==
+  version "5.1.6"
+  resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.6.tgz#07b14e7ab1893a837c8565634960dc398564b1fb"
+  integrity sha512-gjrxYqxz37zWEdMVvQtWPFMFj1dRDb4TGOcgyOfSXTrEXdF92L00WE3C471O3TV/RF1oskcStkXsOU0Ete4s/g==
   dependencies:
     "@types/history" "*"
     "@types/react" "*"
@@ -1946,9 +1945,9 @@
     "@types/react" "*"
 
 "@types/react@*", "@types/react@^16.9.49":
-  version "16.9.49"
-  resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.49.tgz#09db021cf8089aba0cdb12a49f8021a69cce4872"
-  integrity sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==
+  version "16.9.51"
+  resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.51.tgz#f8aa51ffa9996f1387f63686696d9b59713d2b60"
+  integrity sha512-lQa12IyO+DMlnSZ3+AGHRUiUcpK47aakMMoBG8f7HGxJT8Yfe+WE128HIXaHOHVPReAW0oDS3KAI0JI2DDe1PQ==
   dependencies:
     "@types/prop-types" "*"
     csstype "^3.0.2"
@@ -1961,11 +1960,11 @@
     "@types/react" "*"
 
 "@types/recharts@^1.8.15":
-  version "1.8.15"
-  resolved "https://registry.yarnpkg.com/@types/recharts/-/recharts-1.8.15.tgz#02bc06085c9a31a58c00194d15377b45cf506bbf"
-  integrity sha512-ApCfDT/R8RCbZTcl0iqLiKsxVdzE3GzoawTgJUHuQOz6ZXhsPVfp7CNSKI2s3zFwrRRsgmpv2AEcbcZceNHg4w==
+  version "1.8.16"
+  resolved "https://registry.yarnpkg.com/@types/recharts/-/recharts-1.8.16.tgz#3ac3f5513ed61152910f2e828157b21a2761df22"
+  integrity sha512-xBXjOsSJJVP2xGtq/kML4rHGyKxA4x8ZqIU7iwbmWrperpSXzoQ1PWjqf4cBdmcLAWaidDHPJxUVHkZr3R/PEA==
   dependencies:
-    "@types/d3-shape" "*"
+    "@types/d3-shape" "^1"
     "@types/react" "*"
 
 "@types/regenerator-runtime@^0.13.0":
@@ -1973,6 +1972,13 @@
   resolved "https://registry.yarnpkg.com/@types/regenerator-runtime/-/regenerator-runtime-0.13.0.tgz#8b81e6047da0fbb71927c23d749e2a9edf9aad9b"
   integrity sha512-U5osy6qZU3lOPGQRHGDIAt/hC/jAE7zP4Aq258UMXnF8/htmZ+72ALrSsmWTtqML7lCztKkKaPz6Pb5XHJF0Vw==
 
+"@types/responselike@*", "@types/responselike@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
+  integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
+  dependencies:
+    "@types/node" "*"
+
 "@types/semver@^7.3.1":
   version "7.3.4"
   resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb"
@@ -1983,10 +1989,10 @@
   resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
   integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==
 
-"@types/stack-utils@^1.0.1":
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
-  integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
+"@types/stack-utils@^2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
+  integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==
 
 "@types/tapable@*":
   version "1.0.6"
@@ -2048,7 +2054,14 @@
   resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
   integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
 
-"@types/yargs@^15.0.0", "@types/yargs@^15.0.5":
+"@types/yargs@^15.0.0":
+  version "15.0.8"
+  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.8.tgz#7644904cad7427eb704331ea9bf1ee5499b82e23"
+  integrity sha512-b0BYzFUzBpOhPjpl1wtAHU994jBeKF4TKVlT7ssFv44T617XNcPdRoG4AzHLVshLzlrF7i3lTelH7UbuNYV58Q==
+  dependencies:
+    "@types/yargs-parser" "*"
+
+"@types/yargs@^15.0.5":
   version "15.0.5"
   resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79"
   integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==
@@ -2056,124 +2069,60 @@
     "@types/yargs-parser" "*"
 
 "@typescript-eslint/eslint-plugin@^4.0.1":
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.1.1.tgz#78d5b18e259b13c2f4ec41dd9105af269a161a75"
-  integrity sha512-Hoxyt99EA9LMmqo/5PuWWPeWeB3mKyvibfJ1Hy5SfiUpjE8Nqp+5QNd9fOkzL66+fqvIWSIE+Ett16LGMzCGnQ==
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.4.0.tgz#0321684dd2b902c89128405cf0385e9fe8561934"
+  integrity sha512-RVt5wU9H/2H+N/ZrCasTXdGbUTkbf7Hfi9eLiA8vPQkzUJ/bLDCC3CsoZioPrNcnoyN8r0gT153dC++A4hKBQQ==
   dependencies:
-    "@typescript-eslint/experimental-utils" "4.1.1"
-    "@typescript-eslint/scope-manager" "4.1.1"
+    "@typescript-eslint/experimental-utils" "4.4.0"
+    "@typescript-eslint/scope-manager" "4.4.0"
     debug "^4.1.1"
     functional-red-black-tree "^1.0.1"
     regexpp "^3.0.0"
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/experimental-utils@3.10.1":
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686"
-  integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==
-  dependencies:
-    "@types/json-schema" "^7.0.3"
-    "@typescript-eslint/types" "3.10.1"
-    "@typescript-eslint/typescript-estree" "3.10.1"
-    eslint-scope "^5.0.0"
-    eslint-utils "^2.0.0"
-
-"@typescript-eslint/experimental-utils@4.1.1":
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.1.1.tgz#52ff4e37c93113eb96385a4e6d075abece1ea72d"
-  integrity sha512-jzYsNciHoa4Z3c1URtmeT/bamYm8Dwfw6vuN3WHIE/BXb1iC4KveAnXDErTAZtPVxTYBaYn3n2gbt6F6D2rm1A==
-  dependencies:
-    "@types/json-schema" "^7.0.3"
-    "@typescript-eslint/scope-manager" "4.1.1"
-    "@typescript-eslint/types" "4.1.1"
-    "@typescript-eslint/typescript-estree" "4.1.1"
-    eslint-scope "^5.0.0"
-    eslint-utils "^2.0.0"
-
-"@typescript-eslint/experimental-utils@^2.5.0":
-  version "2.34.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f"
-  integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==
+"@typescript-eslint/experimental-utils@4.4.0", "@typescript-eslint/experimental-utils@^4.0.1":
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.4.0.tgz#62a05d3f543b8fc5dec4982830618ea4d030e1a9"
+  integrity sha512-01+OtK/oWeSJTjQcyzDztfLF1YjvKpLFo+JZmurK/qjSRcyObpIecJ4rckDoRCSh5Etw+jKfdSzVEHevh9gJ1w==
   dependencies:
     "@types/json-schema" "^7.0.3"
-    "@typescript-eslint/typescript-estree" "2.34.0"
+    "@typescript-eslint/scope-manager" "4.4.0"
+    "@typescript-eslint/types" "4.4.0"
+    "@typescript-eslint/typescript-estree" "4.4.0"
     eslint-scope "^5.0.0"
     eslint-utils "^2.0.0"
 
-"@typescript-eslint/parser@^3.6.1":
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467"
-  integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==
-  dependencies:
-    "@types/eslint-visitor-keys" "^1.0.0"
-    "@typescript-eslint/experimental-utils" "3.10.1"
-    "@typescript-eslint/types" "3.10.1"
-    "@typescript-eslint/typescript-estree" "3.10.1"
-    eslint-visitor-keys "^1.1.0"
-
-"@typescript-eslint/parser@^4.0.1":
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.1.1.tgz#324b4b35e314075adbc92bd8330cf3ef0c88cf3e"
-  integrity sha512-NLIhmicpKGfJbdXyQBz9j48PA6hq6e+SDOoXy7Ak6bq1ebGqbgG+fR1UIDAuay6OjQdot69c/URu2uLlsP8GQQ==
+"@typescript-eslint/parser@^4.0.1", "@typescript-eslint/parser@^4.2.0":
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.4.0.tgz#65974db9a75f23b036f17b37e959b5f99b659ec0"
+  integrity sha512-yc14iEItCxoGb7W4Nx30FlTyGpU9r+j+n1LUK/exlq2eJeFxczrz/xFRZUk2f6yzWfK+pr1DOTyQnmDkcC4TnA==
   dependencies:
-    "@typescript-eslint/scope-manager" "4.1.1"
-    "@typescript-eslint/types" "4.1.1"
-    "@typescript-eslint/typescript-estree" "4.1.1"
+    "@typescript-eslint/scope-manager" "4.4.0"
+    "@typescript-eslint/types" "4.4.0"
+    "@typescript-eslint/typescript-estree" "4.4.0"
     debug "^4.1.1"
 
-"@typescript-eslint/scope-manager@4.1.1":
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.1.1.tgz#bdb8526e82435f32b4ccd9dd4cec01af97b48850"
-  integrity sha512-0W8TTobCvIIQ2FsrYTffyZGAAFUyIbEHq5EYJb1m7Rpd005jrnOvKOo8ywCLhs/Bm17C+KsrUboBvBAARQVvyA==
-  dependencies:
-    "@typescript-eslint/types" "4.1.1"
-    "@typescript-eslint/visitor-keys" "4.1.1"
-
-"@typescript-eslint/types@3.10.1":
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
-  integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==
-
-"@typescript-eslint/types@4.1.1":
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.1.1.tgz#57500c4a86b28cb47094c1a62f1177ea279a09cb"
-  integrity sha512-zrBiqOKYerMTllKcn+BP+i1b7LW/EbMMYytroXMxUTvFPn1smkCu0D7lSAx29fTUO4jnwV0ljSvYQtn2vNrNxA==
-
-"@typescript-eslint/typescript-estree@2.34.0":
-  version "2.34.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5"
-  integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==
+"@typescript-eslint/scope-manager@4.4.0":
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.4.0.tgz#2f3dd27692a12cc9a046a90ba6a9d8cb7731190a"
+  integrity sha512-r2FIeeU1lmW4K3CxgOAt8djI5c6Q/5ULAgdVo9AF3hPMpu0B14WznBAtxrmB/qFVbVIB6fSx2a+EVXuhSVMEyA==
   dependencies:
-    debug "^4.1.1"
-    eslint-visitor-keys "^1.1.0"
-    glob "^7.1.6"
-    is-glob "^4.0.1"
-    lodash "^4.17.15"
-    semver "^7.3.2"
-    tsutils "^3.17.1"
+    "@typescript-eslint/types" "4.4.0"
+    "@typescript-eslint/visitor-keys" "4.4.0"
 
-"@typescript-eslint/typescript-estree@3.10.1":
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853"
-  integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==
-  dependencies:
-    "@typescript-eslint/types" "3.10.1"
-    "@typescript-eslint/visitor-keys" "3.10.1"
-    debug "^4.1.1"
-    glob "^7.1.6"
-    is-glob "^4.0.1"
-    lodash "^4.17.15"
-    semver "^7.3.2"
-    tsutils "^3.17.1"
+"@typescript-eslint/types@4.4.0":
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.4.0.tgz#63440ef87a54da7399a13bdd4b82060776e9e621"
+  integrity sha512-nU0VUpzanFw3jjX+50OTQy6MehVvf8pkqFcURPAE06xFNFenMj1GPEI6IESvp7UOHAnq+n/brMirZdR+7rCrlA==
 
-"@typescript-eslint/typescript-estree@4.1.1":
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.1.1.tgz#2015a84d71303ecdb6f46efd807ac19a51aab490"
-  integrity sha512-2AUg5v0liVBsqbGxBphbJ0QbGqSRVaF5qPoTPWcxop+66vMdU1h4CCvHxTC47+Qb+Pr4l2RhXDd41JNpwcQEKw==
+"@typescript-eslint/typescript-estree@4.4.0":
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.4.0.tgz#16a2df7c16710ddd5406b32b86b9c1124b1ca526"
+  integrity sha512-Fh85feshKXwki4nZ1uhCJHmqKJqCMba+8ZicQIhNi5d5jSQFteWiGeF96DTjO8br7fn+prTP+t3Cz/a/3yOKqw==
   dependencies:
-    "@typescript-eslint/types" "4.1.1"
-    "@typescript-eslint/visitor-keys" "4.1.1"
+    "@typescript-eslint/types" "4.4.0"
+    "@typescript-eslint/visitor-keys" "4.4.0"
     debug "^4.1.1"
     globby "^11.0.1"
     is-glob "^4.0.1"
@@ -2181,19 +2130,12 @@
     semver "^7.3.2"
     tsutils "^3.17.1"
 
-"@typescript-eslint/visitor-keys@3.10.1":
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931"
-  integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==
-  dependencies:
-    eslint-visitor-keys "^1.1.0"
-
-"@typescript-eslint/visitor-keys@4.1.1":
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.1.1.tgz#bb05664bf4bea28dc120d1da94f3027d42ab0f6f"
-  integrity sha512-/EOOXbA2ferGLG6RmCHEQ0lTTLkOlXYDgblCmQk3tIU7mTPLm4gKhFMeeUSe+bcchTUsKeCk8xcpbop5Zr/8Rw==
+"@typescript-eslint/visitor-keys@4.4.0":
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.4.0.tgz#0a9118344082f14c0f051342a74b42dfdb012640"
+  integrity sha512-oBWeroUZCVsHLiWRdcTXJB7s1nB3taFY8WGvS23tiAlT6jXVvsdAV4rs581bgdEjOhn43q6ro7NkOiLKu6kFqA==
   dependencies:
-    "@typescript-eslint/types" "4.1.1"
+    "@typescript-eslint/types" "4.4.0"
     eslint-visitor-keys "^2.0.0"
 
 "@webassemblyjs/ast@1.9.0":
@@ -2352,9 +2294,9 @@
   integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
 
 abab@^2.0.3:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.4.tgz#6dfa57b417ca06d21b2478f0e638302f99c2405c"
-  integrity sha512-Eu9ELJWCz/c1e9gTiCY+FceWxcqzjYEbqMgtndnuSqZSUCOL73TWNK2mHfIj4Cw2E/ongOp+JISVNCmovt2KYQ==
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
+  integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==
 
 abbrev@1:
   version "1.1.1"
@@ -2400,9 +2342,9 @@ acorn@^6.4.1:
   integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
 
 acorn@^7.1.1, acorn@^7.4.0:
-  version "7.4.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c"
-  integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==
+  version "7.4.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
+  integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
 add@^2.0.6:
   version "2.0.6"
@@ -2447,7 +2389,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
   integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
 
-ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.2, ajv@^6.12.4:
+ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
   version "6.12.5"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"
   integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==
@@ -2457,16 +2399,6 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.2, ajv@^6.12.4:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
-ajv@^6.12.3:
-  version "6.12.4"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234"
-  integrity sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==
-  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"
-
 alphanum-sort@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
@@ -2544,11 +2476,10 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
     color-convert "^1.9.0"
 
 ansi-styles@^4.0.0, ansi-styles@^4.1.0:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
-  integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
   dependencies:
-    "@types/color-name" "^1.1.1"
     color-convert "^2.0.1"
 
 anymatch@^2.0.0:
@@ -3095,16 +3026,16 @@ babel-helpers@^6.24.1:
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-jest@^26.1.0, babel-jest@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.3.0.tgz#10d0ca4b529ca3e7d1417855ef7d7bd6fc0c3463"
-  integrity sha512-sxPnQGEyHAOPF8NcUsD0g7hDCnvLL2XyblRBcgrzTWBB/mAIpWow3n1bEL+VghnnZfreLhFSBsFluRoK2tRK4g==
+babel-jest@^26.1.0, babel-jest@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.5.2.tgz#164f367a35946c6cf54eaccde8762dec50422250"
+  integrity sha512-U3KvymF3SczA3vOL/cgiUFOznfMET+XDIXiWnoJV45siAp2pLMG8i2+/MGZlAC3f/F6Q40LR4M4qDrWZ9wkK8A==
   dependencies:
-    "@jest/transform" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/transform" "^26.5.2"
+    "@jest/types" "^26.5.2"
     "@types/babel__core" "^7.1.7"
     babel-plugin-istanbul "^6.0.0"
-    babel-preset-jest "^26.3.0"
+    babel-preset-jest "^26.5.0"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     slash "^3.0.0"
@@ -3157,10 +3088,10 @@ babel-plugin-istanbul@^6.0.0:
     istanbul-lib-instrument "^4.0.0"
     test-exclude "^6.0.0"
 
-babel-plugin-jest-hoist@^26.2.0:
-  version "26.2.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.2.0.tgz#bdd0011df0d3d513e5e95f76bd53b51147aca2dd"
-  integrity sha512-B/hVMRv8Nh1sQ1a3EY8I0n4Y1Wty3NrR5ebOyVT302op+DOAau+xNEImGMsUWOC3++ZlMooCytKz+NgN8aKGbA==
+babel-plugin-jest-hoist@^26.5.0:
+  version "26.5.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.5.0.tgz#3916b3a28129c29528de91e5784a44680db46385"
+  integrity sha512-ck17uZFD3CDfuwCLATWZxkkuGGFhMij8quP8CNhwj8ek1mqFgbFzRJ30xwC04LLscj/aKsVFfRST+b5PT7rSuw==
   dependencies:
     "@babel/template" "^7.3.3"
     "@babel/types" "^7.3.3"
@@ -3536,9 +3467,9 @@ babel-plugin-transform-strict-mode@^6.24.1:
     babel-types "^6.24.1"
 
 babel-preset-current-node-syntax@^0.1.3:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz#b4b547acddbf963cba555ba9f9cbbb70bfd044da"
-  integrity sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ==
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.4.tgz#826f1f8e7245ad534714ba001f84f7e906c3b615"
+  integrity sha512-5/INNCYhUGqw7VbVjT/hb3ucjgkVHKXY7lX3ZjlN4gm565VyFmJUrJ/h+h16ECVB38R/9SF6aACydpKMLZ/c9w==
   dependencies:
     "@babel/plugin-syntax-async-generators" "^7.8.4"
     "@babel/plugin-syntax-bigint" "^7.8.3"
@@ -3595,12 +3526,12 @@ babel-preset-flow@^6.23.0:
   dependencies:
     babel-plugin-transform-flow-strip-types "^6.22.0"
 
-babel-preset-jest@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.3.0.tgz#ed6344506225c065fd8a0b53e191986f74890776"
-  integrity sha512-5WPdf7nyYi2/eRxCbVrE1kKCWxgWY4RsPEbdJWFm7QsesFGqjdkyLeu1zRkwM1cxK6EPIlNd6d2AxLk7J+t4pw==
+babel-preset-jest@^26.5.0:
+  version "26.5.0"
+  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.5.0.tgz#f1b166045cd21437d1188d29f7fba470d5bdb0e7"
+  integrity sha512-F2vTluljhqkiGSJGBg/jOruA8vIIIL11YrxRcO7nviNTMbbofPSHwnm8mgP7d/wS7wRSexRoI6X1A6T74d4LQA==
   dependencies:
-    babel-plugin-jest-hoist "^26.2.0"
+    babel-plugin-jest-hoist "^26.5.0"
     babel-preset-current-node-syntax "^0.1.3"
 
 babel-preset-react@^6.24.1:
@@ -3852,7 +3783,7 @@ boolbase@^1.0.0, boolbase@~1.0.0:
   resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
   integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
 
-boolean@^3.0.0, boolean@^3.0.1:
+boolean@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.1.tgz#35ecf2b4a2ee191b0b44986f14eb5f052a5cbb4f"
   integrity sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==
@@ -3998,7 +3929,7 @@ browserslist@^3.2.6:
     caniuse-lite "^1.0.30000844"
     electron-to-chromium "^1.3.47"
 
-browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.8.5:
+browserslist@^4.0.0, browserslist@^4.8.5:
   version "4.14.3"
   resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.3.tgz#381f9e7f13794b2eb17e1761b4f118e8ae665a53"
   integrity sha512-GcZPC5+YqyPO4SFnz48/B0YaCwS47Q9iPChRGi6t7HhflKBcINzFrJvRfC+jp30sRMKxF+d4EHGs27Z0XP1NaQ==
@@ -4008,6 +3939,16 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.8.5:
     escalade "^3.1.0"
     node-releases "^1.1.61"
 
+browserslist@^4.12.0:
+  version "4.14.5"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.5.tgz#1c751461a102ddc60e40993639b709be7f2c4015"
+  integrity sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==
+  dependencies:
+    caniuse-lite "^1.0.30001135"
+    electron-to-chromium "^1.3.571"
+    escalade "^3.1.0"
+    node-releases "^1.1.61"
+
 browserslist@^4.12.2:
   version "4.14.0"
   resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.0.tgz#2908951abfe4ec98737b72f34c3bcedc8d43b000"
@@ -4156,6 +4097,11 @@ cache-base@^1.0.1:
     union-value "^1.0.0"
     unset-value "^1.0.0"
 
+cacheable-lookup@^5.0.3:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz#049fdc59dffdd4fc285e8f4f82936591bd59fec3"
+  integrity sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w==
+
 cacheable-request@^6.0.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
@@ -4169,6 +4115,19 @@ cacheable-request@^6.0.0:
     normalize-url "^4.1.0"
     responselike "^1.0.2"
 
+cacheable-request@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58"
+  integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==
+  dependencies:
+    clone-response "^1.0.2"
+    get-stream "^5.1.0"
+    http-cache-semantics "^4.0.0"
+    keyv "^4.0.0"
+    lowercase-keys "^2.0.0"
+    normalize-url "^4.1.0"
+    responselike "^2.0.0"
+
 call-me-maybe@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
@@ -4251,11 +4210,16 @@ caniuse-db@^1.0.30001090:
   resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001118.tgz#60f56de236d7b68859d9a377a7f2f69092283247"
   integrity sha512-yMoV0unAwrdNkqKbNI4jlyUHrLsBYrzf9IOYTNxhy3t1onqpLK2nH0IPHzr2isrmmY8pQ6UPqFGka/8Gc8Yt9w==
 
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001111, caniuse-lite@^1.0.30001131:
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001111:
   version "1.0.30001132"
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001132.tgz#309279274f10d3aa736aa91fa269fcc8d0cd7ef9"
   integrity sha512-zk5FXbnsmHa0Ktc/NOZJRr+ilXva+2KFJuRiQfnjkxJfV/7DYP5C27lSQF++/veCUzVWE5xecZnSBJjf6fSwJA==
 
+caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001131, caniuse-lite@^1.0.30001135:
+  version "1.0.30001146"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001146.tgz#c61fcb1474520c1462913689201fb292ba6f447c"
+  integrity sha512-VAy5RHDfTJhpxnDdp2n40GPPLp3KqNrXz1QqFv4J64HvArKs8nuNMOWkB3ICOaBTU/Aj4rYAo/ytdQDDFF/Pug==
+
 capture-exit@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
@@ -4296,7 +4260,7 @@ chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
-chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.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==
@@ -4483,13 +4447,6 @@ cli-boxes@^2.2.0:
   resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
   integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
 
-cli-cursor@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
-  integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
-  dependencies:
-    restore-cursor "^2.0.0"
-
 cli-cursor@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
@@ -4497,7 +4454,7 @@ cli-cursor@^3.1.0:
   dependencies:
     restore-cursor "^3.1.0"
 
-cli-spinners@^2.0.0:
+cli-spinners@^2.4.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.4.0.tgz#c6256db216b878cfba4720e719cec7cf72685d7f"
   integrity sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA==
@@ -4528,6 +4485,15 @@ cliui@^6.0.0:
     strip-ansi "^6.0.0"
     wrap-ansi "^6.2.0"
 
+cliui@^7.0.0:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.1.tgz#a4cb67aad45cd83d8d05128fc9f4d8fbb887e6b3"
+  integrity sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ==
+  dependencies:
+    string-width "^4.2.0"
+    strip-ansi "^6.0.0"
+    wrap-ansi "^7.0.0"
+
 clone-deep@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
@@ -4993,24 +4959,23 @@ css-declaration-sorter@^4.0.1:
     postcss "^7.0.1"
     timsort "^0.3.0"
 
-css-loader@^3.6.0:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645"
-  integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==
+css-loader@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.3.0.tgz#c888af64b2a5b2e85462c72c0f4a85c7e2e0821e"
+  integrity sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg==
   dependencies:
-    camelcase "^5.3.1"
+    camelcase "^6.0.0"
     cssesc "^3.0.0"
     icss-utils "^4.1.1"
-    loader-utils "^1.2.3"
-    normalize-path "^3.0.0"
+    loader-utils "^2.0.0"
     postcss "^7.0.32"
     postcss-modules-extract-imports "^2.0.0"
-    postcss-modules-local-by-default "^3.0.2"
+    postcss-modules-local-by-default "^3.0.3"
     postcss-modules-scope "^2.2.0"
     postcss-modules-values "^3.0.0"
     postcss-value-parser "^4.1.0"
-    schema-utils "^2.7.0"
-    semver "^6.3.0"
+    schema-utils "^2.7.1"
+    semver "^7.3.2"
 
 css-select-base-adapter@^0.1.1:
   version "0.1.1"
@@ -5203,6 +5168,11 @@ d3-array@^1.2.0:
   resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
   integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
 
+d3-array@^2.4.0:
+  version "2.8.0"
+  resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.8.0.tgz#f76e10ad47f1f4f75f33db5fc322eb9ffde5ef23"
+  integrity sha512-6V272gsOeg7+9pTW1jSYOR1QE37g95I3my1hBmY+vOUNHRrk9yt4OTz/gK7PMkVAVDrYYq4mq3grTiZ8iJdNIw==
+
 d3-collection@1:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e"
@@ -5213,12 +5183,17 @@ d3-color@1:
   resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a"
   integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==
 
+d3-ease@^1.0.0:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.7.tgz#9a834890ef8b8ae8c558b2fe55bd57f5993b85e2"
+  integrity sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==
+
 d3-format@1:
   version "1.4.5"
   resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
   integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
 
-d3-interpolate@1, d3-interpolate@^1.3.0:
+d3-interpolate@1, d3-interpolate@^1.1.1, d3-interpolate@^1.3.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987"
   integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==
@@ -5230,6 +5205,19 @@ d3-path@1:
   resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
   integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
 
+d3-scale@^1.0.0:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d"
+  integrity sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==
+  dependencies:
+    d3-array "^1.2.0"
+    d3-collection "1"
+    d3-color "1"
+    d3-format "1"
+    d3-interpolate "1"
+    d3-time "1"
+    d3-time-format "2"
+
 d3-scale@^2.1.0:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f"
@@ -5242,7 +5230,7 @@ d3-scale@^2.1.0:
     d3-time "1"
     d3-time-format "2"
 
-d3-shape@^1.2.0:
+d3-shape@^1.0.0, d3-shape@^1.2.0:
   version "1.3.7"
   resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
   integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
@@ -5261,6 +5249,16 @@ d3-time@1:
   resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1"
   integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==
 
+d3-timer@^1.0.0:
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5"
+  integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==
+
+d3-voronoi@^1.1.2:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.4.tgz#dd3c78d7653d2bb359284ae478645d95944c8297"
+  integrity sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==
+
 damerau-levenshtein@^1.0.6:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791"
@@ -5287,7 +5285,7 @@ date-fns@^2.0.1:
   resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b"
   integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==
 
-debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.5.1, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, 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==
@@ -5301,7 +5299,7 @@ debug@4.1.1:
   dependencies:
     ms "^2.1.1"
 
-debug@^3.1.1, debug@^3.2.5:
+debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
   integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -5334,9 +5332,9 @@ decimal.js-light@^2.4.1:
   integrity sha512-b3VJCbd2hwUpeRGG3Toob+CRo8W22xplipNhP3tN7TSVB/cyMX71P1vM2Xjc9H74uV6dS2hDDmo/rHq8L87Upg==
 
 decimal.js@^10.2.0:
-  version "10.2.0"
-  resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231"
-  integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==
+  version "10.2.1"
+  resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3"
+  integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==
 
 decode-uri-component@^0.2.0:
   version "0.2.0"
@@ -5350,6 +5348,13 @@ decompress-response@^3.3.0:
   dependencies:
     mimic-response "^1.0.0"
 
+decompress-response@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
+  integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+  dependencies:
+    mimic-response "^3.1.0"
+
 dedent@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.4.0.tgz#87defd040bd4c1595d963282ec57f3c2a8525642"
@@ -5419,6 +5424,11 @@ defer-to-connect@^1.0.1:
   resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
   integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
 
+defer-to-connect@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1"
+  integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==
+
 define-properties@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@@ -5487,6 +5497,18 @@ del@^5.1.0:
     rimraf "^3.0.0"
     slash "^3.0.0"
 
+delaunator@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-4.0.1.tgz#3d779687f57919a7a418f8ab947d3bddb6846957"
+  integrity sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==
+
+delaunay-find@0.0.5:
+  version "0.0.5"
+  resolved "https://registry.yarnpkg.com/delaunay-find/-/delaunay-find-0.0.5.tgz#5fb37e6509da934881b4b16c08898ac89862c097"
+  integrity sha512-7yAJ/wmKWj3SgqjtkGqT/RCwI0HWAo5YnHMoF5nYXD8cdci+YSo23iPmgrZUNOpDxRWN91PqxUvMMr2lKpjr+w==
+  dependencies:
+    delaunator "^4.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"
@@ -5532,7 +5554,7 @@ detect-indent@^4.0.0:
   dependencies:
     repeating "^2.0.0"
 
-detect-libc@^1.0.3:
+detect-libc@^1.0.2, detect-libc@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
   integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
@@ -5565,10 +5587,10 @@ diff-sequences@^25.2.6:
   resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
   integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
 
-diff-sequences@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.3.0.tgz#62a59b1b29ab7fd27cef2a33ae52abe73042d0a2"
-  integrity sha512-5j5vdRcw3CNctePNYN0Wy2e/JbWT6cAYnXv5OuqPhDpyCGc0uLu2TK0zOCJWNB9kOIfYMSpIulRaDgIi4HJ6Ig==
+diff-sequences@^26.5.0:
+  version "26.5.0"
+  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.5.0.tgz#ef766cf09d43ed40406611f11c6d8d9dd8b2fefd"
+  integrity sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q==
 
 diff@^4.0.1, diff@^4.0.2:
   version "4.0.2"
@@ -5858,22 +5880,30 @@ electron-react-devtools@^0.5.3:
   resolved "https://registry.yarnpkg.com/electron-react-devtools/-/electron-react-devtools-0.5.3.tgz#c74edb1245dc1cfe1380b93016cd4eb588ed00b7"
   integrity sha1-x07bEkXcHP4TgLkwFs1OtYjtALc=
 
-electron-rebuild@^1.10.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/electron-rebuild/-/electron-rebuild-1.11.0.tgz#e384773a9ad30fe0a6a5bbb326b779d51f668b6a"
-  integrity sha512-cn6AqZBQBVtaEyj5jZW1/LOezZZ22PA1HvhEP7asvYPJ8PDF4i4UFt9be4i9T7xJKiSiomXvY5Fd+dSq3FXZxA==
+electron-rebuild@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/electron-rebuild/-/electron-rebuild-2.2.0.tgz#c7e8ab2da2d5a6d263b03bf032ff480b7341cfab"
+  integrity sha512-qbrCoBSmbL/f6OwfRXg5cihAJ0TwbgRKmyK7helR6XNlaoPO42ny/+4yCTXJrYa0ZhkvcdY+gZE/wu2p19gFHg==
   dependencies:
+    "@malept/cross-spawn-promise" "^1.1.0"
     colors "^1.3.3"
     debug "^4.1.1"
     detect-libc "^1.0.3"
-    fs-extra "^8.1.0"
-    node-abi "^2.11.0"
-    node-gyp "^6.0.1"
-    ora "^3.4.0"
-    spawn-rx "^3.0.0"
-    yargs "^14.2.0"
-
-electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.523, electron-to-chromium@^1.3.570:
+    fs-extra "^9.0.1"
+    got "^11.7.0"
+    lzma-native "^6.0.1"
+    node-abi "^2.19.1"
+    node-gyp "^7.1.0"
+    ora "^5.1.0"
+    tar "^6.0.5"
+    yargs "^16.0.0"
+
+electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.570, electron-to-chromium@^1.3.571:
+  version "1.3.578"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.578.tgz#e6671936f4571a874eb26e2e833aa0b2c0b776e0"
+  integrity sha512-z4gU6dA1CbBJsAErW5swTGAaU2TBzc2mPAonJb00zqW1rOraDo2zfBMDRvaz9cVic+0JEZiYbHWPw/fTaZlG2Q==
+
+electron-to-chromium@^1.3.523:
   version "1.3.570"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.570.tgz#3f5141cc39b4e3892a276b4889980dabf1d29c7f"
   integrity sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg==
@@ -5892,9 +5922,9 @@ electron-updater@^4.3.5:
     semver "^7.3.2"
 
 electron@^10:
-  version "10.1.2"
-  resolved "https://registry.yarnpkg.com/electron/-/electron-10.1.2.tgz#30b6fd7669f8daf08c56219a61dfa053fa2b0c70"
-  integrity sha512-SvN8DcKCmPZ0UcQSNAJBfaUu+LGACqtRhUn1rW0UBLHgdbbDM76L0GU5/XGQEllH5pu5bwlCZwax3srzIl+Aeg==
+  version "10.1.3"
+  resolved "https://registry.yarnpkg.com/electron/-/electron-10.1.3.tgz#7e276e373bf30078bd4cb1184850a91268dc0e6c"
+  integrity sha512-CR8LrlG47MdAp317SQ3vGYa2o2cIMdMSMPYH46OVitFLk35dwE9fn3VqvhUIXhCHYcNWIAPzMhkVHpkoFdKWuw==
   dependencies:
     "@electron/get" "^1.0.1"
     "@types/node" "^12.0.12"
@@ -6021,9 +6051,9 @@ env-paths@^2.2.0:
   integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
 
 enzyme-adapter-react-16@^1.15.2:
-  version "1.15.4"
-  resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.4.tgz#328a782365a363ecb424f99283c4833dd92c0f21"
-  integrity sha512-wPzxs+JaGDK2TPYzl5a9YWGce6i2SQ3Cg51ScLeyj2WotUZ8Obcq1ke/U1Y2VGpYlb9rrX2yCjzSMgtKCeAt5w==
+  version "1.15.5"
+  resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.5.tgz#7a6f0093d3edd2f7025b36e7fbf290695473ee04"
+  integrity sha512-33yUJGT1nHFQlbVI5qdo5Pfqvu/h4qPwi1o0a6ZZsjpiqq92a3HjynDhwd1IeED+Su60HDWV8mxJqkTnLYdGkw==
   dependencies:
     enzyme-adapter-utils "^1.13.1"
     enzyme-shallow-equal "^1.0.4"
@@ -6056,10 +6086,11 @@ enzyme-shallow-equal@^1.0.1, enzyme-shallow-equal@^1.0.4:
     object-is "^1.1.2"
 
 enzyme-to-json@^3.5.0:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.5.0.tgz#3d536f1e8fb50d972360014fe2bd64e6a672f7dd"
-  integrity sha512-clusXRsiaQhG7+wtyc4t7MU8N3zCOgf4eY9+CeSenYzKlFST4lxerfOvnWd4SNaToKhkuba+w6m242YpQOS7eA==
+  version "3.6.1"
+  resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.6.1.tgz#d60740950bc7ca6384dfe6fe405494ec5df996bc"
+  integrity sha512-15tXuONeq5ORoZjV/bUo2gbtZrN2IH+Z6DvL35QmZyKHgbY1ahn6wcnLd9Xv9OjiwbAXiiP8MRZwbZrCv1wYNg==
   dependencies:
+    "@types/cheerio" "^0.22.22"
     lodash "^4.17.15"
     react-is "^16.12.0"
 
@@ -6119,7 +6150,24 @@ error-stack-parser@^2.0.6:
   dependencies:
     stackframe "^1.1.1"
 
-es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5:
+es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5:
+  version "1.17.7"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c"
+  integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==
+  dependencies:
+    es-to-primitive "^1.2.1"
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    has-symbols "^1.0.1"
+    is-callable "^1.2.2"
+    is-regex "^1.1.1"
+    object-inspect "^1.8.0"
+    object-keys "^1.1.1"
+    object.assign "^4.1.1"
+    string.prototype.trimend "^1.0.1"
+    string.prototype.trimstart "^1.0.1"
+
+es-abstract@^1.17.2:
   version "1.17.6"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
   integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
@@ -6136,21 +6184,21 @@ es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstrac
     string.prototype.trimend "^1.0.1"
     string.prototype.trimstart "^1.0.1"
 
-es-abstract@^1.18.0-next.0:
-  version "1.18.0-next.0"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.0.tgz#b302834927e624d8e5837ed48224291f2c66e6fc"
-  integrity sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==
+es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1:
+  version "1.18.0-next.1"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68"
+  integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==
   dependencies:
     es-to-primitive "^1.2.1"
     function-bind "^1.1.1"
     has "^1.0.3"
     has-symbols "^1.0.1"
-    is-callable "^1.2.0"
+    is-callable "^1.2.2"
     is-negative-zero "^2.0.0"
     is-regex "^1.1.1"
     object-inspect "^1.8.0"
     object-keys "^1.1.1"
-    object.assign "^4.1.0"
+    object.assign "^4.1.1"
     string.prototype.trimend "^1.0.1"
     string.prototype.trimstart "^1.0.1"
 
@@ -6219,12 +6267,12 @@ eslint-config-airbnb-base@^14.2.0:
     object.assign "^4.1.0"
     object.entries "^1.1.2"
 
-eslint-config-airbnb-typescript@^9.0.0:
-  version "9.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-9.0.0.tgz#2524f3fa6fceb3df4ae191d1e1114a04fe54c6e6"
-  integrity sha512-BxckAZU4rwfOidZVucAO120fTSGQAugimS8HFp7OoiordpyNkq5bxSlTPZ2XxSY8Q2NWDIygqtJKqupZld/TXA==
+eslint-config-airbnb-typescript@^11.0.0:
+  version "11.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-11.0.0.tgz#c5a95f6b4b3be4f8791d8769b21f6cede340e428"
+  integrity sha512-9PtEcJow7ghv+NuqCg1huVB6X28QNxjGUeB7scIKXdBqY/z7IGg6EwMq0XJCKY5cZlOVLH+0WmKoC2HB9MszJA==
   dependencies:
-    "@typescript-eslint/parser" "^3.6.1"
+    "@typescript-eslint/parser" "^4.2.0"
     eslint-config-airbnb "^18.2.0"
     eslint-config-airbnb-base "^14.2.0"
 
@@ -6238,13 +6286,13 @@ eslint-config-airbnb@^18.2.0:
     object.entries "^1.1.2"
 
 eslint-config-prettier@^6.11.0:
-  version "6.11.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz#f6d2238c1290d01c859a8b5c1f7d352a0b0da8b1"
-  integrity sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==
+  version "6.12.0"
+  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.12.0.tgz#9eb2bccff727db1c52104f0b49e87ea46605a0d2"
+  integrity sha512-9jWPlFlgNwRUYVoujvWTQ1aMO8o6648r+K7qU7K5Jmkbyqav1fuEZC0COYpGBxyiAJb65Ra9hrmFx19xRGwXWw==
   dependencies:
     get-stdin "^6.0.0"
 
-eslint-import-resolver-node@^0.3.3:
+eslint-import-resolver-node@^0.3.4:
   version "0.3.4"
   resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717"
   integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==
@@ -6252,10 +6300,10 @@ eslint-import-resolver-node@^0.3.3:
     debug "^2.6.9"
     resolve "^1.13.1"
 
-eslint-import-resolver-webpack@^0.12.2:
-  version "0.12.2"
-  resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.12.2.tgz#769e86cd0c752a1536c19855ebd90aa14ce384ee"
-  integrity sha512-7Jnm4YAoNNkvqPaZkKdIHsKGmv8/uNnYC5QsXkiSodvX4XEEfH2AKOna98FK52fCDXm3q4HzuX+7pRMKkJ64EQ==
+eslint-import-resolver-webpack@^0.13.0:
+  version "0.13.0"
+  resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.0.tgz#5cb19cf4b6996c8a2514aeb10f909e2c70488dc3"
+  integrity sha512-hZWGcmjaJZK/WSCYGI/y4+FMGQZT+cwW/1E/P4rDwFj2PbanlQHISViw4ccDJ+2wxAqjgwBfxwy3seABbVKDEw==
   dependencies:
     array-find "^1.0.0"
     debug "^2.6.9"
@@ -6291,16 +6339,16 @@ eslint-plugin-compat@^3.8.0:
     semver "7.3.2"
 
 eslint-plugin-import@^2.22.0:
-  version "2.22.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e"
-  integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==
+  version "2.22.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702"
+  integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==
   dependencies:
     array-includes "^3.1.1"
     array.prototype.flat "^1.2.3"
     contains-path "^0.1.0"
     debug "^2.6.9"
     doctrine "1.5.0"
-    eslint-import-resolver-node "^0.3.3"
+    eslint-import-resolver-node "^0.3.4"
     eslint-module-utils "^2.6.0"
     has "^1.0.3"
     minimatch "^3.0.4"
@@ -6309,12 +6357,12 @@ eslint-plugin-import@^2.22.0:
     resolve "^1.17.0"
     tsconfig-paths "^3.9.0"
 
-eslint-plugin-jest@^23.18.0:
-  version "23.20.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.20.0.tgz#e1d69c75f639e99d836642453c4e75ed22da4099"
-  integrity sha512-+6BGQt85OREevBDWCvhqj1yYA4+BFK4XnRZSGJionuEYmcglMZYLNNBBemwzbqUAckURaHdJSBcjHPyrtypZOw==
+eslint-plugin-jest@^24.1.0:
+  version "24.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.1.0.tgz#6708037d7602e5288ce877fd0103f329dc978361"
+  integrity sha512-827YJ+E8B9PvXu/0eiVSNFfxxndbKv+qE/3GSMhdorCaeaOehtqHGX2YDW9B85TEOre9n/zscledkFW/KbnyGg==
   dependencies:
-    "@typescript-eslint/experimental-utils" "^2.5.0"
+    "@typescript-eslint/experimental-utils" "^4.0.1"
 
 eslint-plugin-jsx-a11y@6.3.1:
   version "6.3.1"
@@ -6351,15 +6399,15 @@ eslint-plugin-react-hooks@^4.0.8:
   integrity sha512-ykUeqkGyUGgwTtk78C0o8UG2fzwmgJ0qxBGPp2WqRKsTwcLuVf01kTDRAtOsd4u6whX2XOC8749n2vPydP82fg==
 
 eslint-plugin-react@^7.20.3:
-  version "7.20.6"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.6.tgz#4d7845311a93c463493ccfa0a19c9c5d0fd69f60"
-  integrity sha512-kidMTE5HAEBSLu23CUDvj8dc3LdBU0ri1scwHBZjI41oDv4tjsWZKU7MQccFzH1QYPYhsnTF2ovh7JlcIcmxgg==
+  version "7.21.4"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.4.tgz#31060b2e5ff82b12e24a3cc33edb7d12f904775c"
+  integrity sha512-uHeQ8A0hg0ltNDXFu3qSfFqTNPXm1XithH6/SY318UX76CMj7Q599qWpgmMhVQyvhq36pm7qvoN3pb6/3jsTFg==
   dependencies:
     array-includes "^3.1.1"
     array.prototype.flatmap "^1.2.3"
     doctrine "^2.1.0"
     has "^1.0.3"
-    jsx-ast-utils "^2.4.1"
+    jsx-ast-utils "^2.4.1 || ^3.0.0"
     object.entries "^1.1.2"
     object.fromentries "^2.0.2"
     object.values "^1.1.1"
@@ -6380,7 +6428,7 @@ eslint-scope@^4.0.3:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
-eslint-scope@^5.0.0, eslint-scope@^5.1.0:
+eslint-scope@^5.0.0, 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==
@@ -6406,9 +6454,9 @@ eslint-visitor-keys@^2.0.0:
   integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
 
 eslint@^7.9.0:
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.9.0.tgz#522aeccc5c3a19017cf0cb46ebfd660a79acf337"
-  integrity sha512-V6QyhX21+uXp4T+3nrNfI3hQNBDa/P8ga7LoQOenwrlEFXrEnUEE+ok1dMtaS3b6rmLXhT1TkTIsG75HMLbknA==
+  version "7.11.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.11.0.tgz#aaf2d23a0b5f1d652a08edacea0c19f7fadc0b3b"
+  integrity sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==
   dependencies:
     "@babel/code-frame" "^7.0.0"
     "@eslint/eslintrc" "^0.1.3"
@@ -6418,9 +6466,9 @@ eslint@^7.9.0:
     debug "^4.0.1"
     doctrine "^3.0.0"
     enquirer "^2.3.5"
-    eslint-scope "^5.1.0"
+    eslint-scope "^5.1.1"
     eslint-utils "^2.1.0"
-    eslint-visitor-keys "^1.3.0"
+    eslint-visitor-keys "^2.0.0"
     espree "^7.3.0"
     esquery "^1.2.0"
     esutils "^2.0.2"
@@ -6614,16 +6662,16 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
   dependencies:
     homedir-polyfill "^1.0.1"
 
-expect@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/expect/-/expect-26.4.2.tgz#36db120928a5a2d7d9736643032de32f24e1b2a1"
-  integrity sha512-IlJ3X52Z0lDHm7gjEp+m76uX46ldH5VpqmU0006vqDju/285twh7zaWMRhs67VpQhBwjjMchk+p5aA0VkERCAA==
+expect@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/expect/-/expect-26.5.2.tgz#3e0631c4a657a83dbec769ad246a2998953a55a6"
+  integrity sha512-ccTGrXZd8DZCcvCz4htGXTkd/LOoy6OEtiDS38x3/VVf6E4AQL0QoeksBiw7BtGR5xDNiRYPB8GN6pfbuTOi7w==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     ansi-styles "^4.0.0"
     jest-get-type "^26.3.0"
-    jest-matcher-utils "^26.4.2"
-    jest-message-util "^26.3.0"
+    jest-matcher-utils "^26.5.2"
+    jest-message-util "^26.5.2"
     jest-regex-util "^26.0.0"
 
 express@^4.16.3, express@^4.17.1:
@@ -6840,12 +6888,12 @@ file-entry-cache@^5.0.1:
     flat-cache "^2.0.1"
 
 file-loader@^6.0.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.1.0.tgz#65b9fcfb0ea7f65a234a1f10cdd7f1ab9a33f253"
-  integrity sha512-26qPdHyTsArQ6gU4P1HJbAbnFTyT2r0pG7czh1GFAd9TZbj0n94wWbupgixZH/ET/meqi2/5+F7DhW4OAXD+Lg==
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.1.1.tgz#a6f29dfb3f5933a1c350b2dbaa20ac5be0539baa"
+  integrity sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==
   dependencies:
     loader-utils "^2.0.0"
-    schema-utils "^2.7.1"
+    schema-utils "^3.0.0"
 
 file-uri-to-path@1.0.0:
   version "1.0.0"
@@ -7152,7 +7200,7 @@ gensync@^1.0.0-beta.1:
   resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
   integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==
 
-get-caller-file@^2.0.1:
+get-caller-file@^2.0.1, 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==
@@ -7233,7 +7281,7 @@ glob-to-regexp@^0.3.0:
   resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
   integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
 
-glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
   version "7.1.6"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
   integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@@ -7411,6 +7459,23 @@ google-protobuf@3.11.4:
   resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.11.4.tgz#598ca405a3cfa917a2132994d008b5932ef42014"
   integrity sha512-lL6b04rDirurUBOgsY2+LalI6Evq8eH5TcNzi7TYQ3BsIWelT0KSOQSBsXuavEkNf+odQU6c0lgz3UsZXeNX9Q==
 
+got@^11.7.0:
+  version "11.7.0"
+  resolved "https://registry.yarnpkg.com/got/-/got-11.7.0.tgz#a386360305571a74548872e674932b4ef70d3b24"
+  integrity sha512-7en2XwH2MEqOsrK0xaKhbWibBoZqy+f1RSUoIeF1BLcnf+pyQdDsljWMfmOh+QKJwuvDIiKx38GtPh5wFdGGjg==
+  dependencies:
+    "@sindresorhus/is" "^3.1.1"
+    "@szmarczak/http-timer" "^4.0.5"
+    "@types/cacheable-request" "^6.0.1"
+    "@types/responselike" "^1.0.0"
+    cacheable-lookup "^5.0.3"
+    cacheable-request "^7.0.1"
+    decompress-response "^6.0.0"
+    http2-wrapper "^1.0.0-beta.5.2"
+    lowercase-keys "^2.0.0"
+    p-cancelable "^2.0.0"
+    responselike "^2.0.0"
+
 got@^9.6.0:
   version "9.6.0"
   resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
@@ -7433,7 +7498,7 @@ graceful-fs@4.1.4:
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.4.tgz#ef089d2880f033b011823ce5c8fae798da775dbd"
   integrity sha1-7widKIDwM7ARgjzlyPrnmNp3Xb0=
 
-graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4:
+graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4:
   version "4.2.4"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
   integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
@@ -7587,7 +7652,7 @@ highlight-es@^1.0.0:
     is-es2016-keyword "^1.0.0"
     js-tokens "^3.0.0"
 
-history@^4.7.2, history@^4.9.0:
+history@^4.9.0:
   version "4.10.1"
   resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
   integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
@@ -7599,6 +7664,13 @@ history@^4.7.2, history@^4.9.0:
     tiny-warning "^1.0.0"
     value-equal "^1.0.1"
 
+history@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/history/-/history-5.0.0.tgz#0cabbb6c4bbf835addb874f8259f6d25101efd08"
+  integrity sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg==
+  dependencies:
+    "@babel/runtime" "^7.7.6"
+
 hmac-drbg@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -7788,6 +7860,14 @@ http-signature@~1.2.0:
     jsprim "^1.2.2"
     sshpk "^1.7.0"
 
+http2-wrapper@^1.0.0-beta.5.2:
+  version "1.0.0-beta.5.2"
+  resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz#8b923deb90144aea65cf834b016a340fc98556f3"
+  integrity sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==
+  dependencies:
+    quick-lru "^5.1.1"
+    resolve-alpn "^1.0.0"
+
 https-browserify@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
@@ -7819,7 +7899,7 @@ hyphenate-style-name@^1.0.3:
   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:
+iconv-lite@0.4.24, iconv-lite@^0.4.4:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -7864,6 +7944,13 @@ iferr@^0.1.5:
   resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
   integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
 
+ignore-walk@^3.0.1:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"
+  integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==
+  dependencies:
+    minimatch "^3.0.4"
+
 ignore@^4.0.3, ignore@^4.0.6:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
@@ -8113,10 +8200,10 @@ is-buffer@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623"
   integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==
 
-is-callable@^1.1.4, is-callable@^1.2.0:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.1.tgz#4d1e21a4f437509d25ce55f8184350771421c96d"
-  integrity sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==
+is-callable@^1.1.4, is-callable@^1.2.0, is-callable@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
+  integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==
 
 is-callable@^1.1.5:
   version "1.2.0"
@@ -8294,6 +8381,11 @@ is-installed-globally@^0.3.1:
     global-dirs "^2.0.1"
     is-path-inside "^3.0.1"
 
+is-interactive@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
+  integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
+
 is-jquery-obj@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/is-jquery-obj/-/is-jquery-obj-0.1.1.tgz#e8d9cc9737b1ab0733b50303e33a38ed7cc2f60b"
@@ -8600,57 +8692,57 @@ jake@^10.6.1:
     filelist "^1.0.1"
     minimatch "^3.0.4"
 
-jest-changed-files@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.3.0.tgz#68fb2a7eb125f50839dab1f5a17db3607fe195b1"
-  integrity sha512-1C4R4nijgPltX6fugKxM4oQ18zimS7LqQ+zTTY8lMCMFPrxqBFb7KJH0Z2fRQJvw2Slbaipsqq7s1mgX5Iot+g==
+jest-changed-files@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.5.2.tgz#330232c6a5c09a7f040a5870e8f0a9c6abcdbed5"
+  integrity sha512-qSmssmiIdvM5BWVtyK/nqVpN3spR5YyvkvPqz1x3BR1bwIxsWmU/MGwLoCrPNLbkG2ASAKfvmJpOduEApBPh2w==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     execa "^4.0.0"
     throat "^5.0.0"
 
-jest-cli@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.4.2.tgz#24afc6e4dfc25cde4c7ec4226fb7db5f157c21da"
-  integrity sha512-zb+lGd/SfrPvoRSC/0LWdaWCnscXc1mGYW//NP4/tmBvRPT3VntZ2jtKUONsRi59zc5JqmsSajA9ewJKFYp8Cw==
+jest-cli@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.5.2.tgz#0df114399b4036a3f046f0a9f25c50372c76b3a2"
+  integrity sha512-usm48COuUvRp8YEG5OWOaxbSM0my7eHn3QeBWxiGUuFhvkGVBvl1fic4UjC02EAEQtDv8KrNQUXdQTV6ZZBsoA==
   dependencies:
-    "@jest/core" "^26.4.2"
-    "@jest/test-result" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/core" "^26.5.2"
+    "@jest/test-result" "^26.5.2"
+    "@jest/types" "^26.5.2"
     chalk "^4.0.0"
     exit "^0.1.2"
     graceful-fs "^4.2.4"
     import-local "^3.0.2"
     is-ci "^2.0.0"
-    jest-config "^26.4.2"
-    jest-util "^26.3.0"
-    jest-validate "^26.4.2"
+    jest-config "^26.5.2"
+    jest-util "^26.5.2"
+    jest-validate "^26.5.2"
     prompts "^2.0.1"
-    yargs "^15.3.1"
+    yargs "^15.4.1"
 
-jest-config@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.4.2.tgz#da0cbb7dc2c131ffe831f0f7f2a36256e6086558"
-  integrity sha512-QBf7YGLuToiM8PmTnJEdRxyYy3mHWLh24LJZKVdXZ2PNdizSe1B/E8bVm+HYcjbEzGuVXDv/di+EzdO/6Gq80A==
+jest-config@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.5.2.tgz#6e828e25f10124433dd008fbd83348636de0972a"
+  integrity sha512-dqJOnSegNdE5yDiuGHsjTM5gec7Z4AcAMHiW+YscbOYJAlb3LEtDSobXCq0or9EmGQI5SFmKy4T7P1FxetJOfg==
   dependencies:
     "@babel/core" "^7.1.0"
-    "@jest/test-sequencer" "^26.4.2"
-    "@jest/types" "^26.3.0"
-    babel-jest "^26.3.0"
+    "@jest/test-sequencer" "^26.5.2"
+    "@jest/types" "^26.5.2"
+    babel-jest "^26.5.2"
     chalk "^4.0.0"
     deepmerge "^4.2.2"
     glob "^7.1.1"
     graceful-fs "^4.2.4"
-    jest-environment-jsdom "^26.3.0"
-    jest-environment-node "^26.3.0"
+    jest-environment-jsdom "^26.5.2"
+    jest-environment-node "^26.5.2"
     jest-get-type "^26.3.0"
-    jest-jasmine2 "^26.4.2"
+    jest-jasmine2 "^26.5.2"
     jest-regex-util "^26.0.0"
-    jest-resolve "^26.4.0"
-    jest-util "^26.3.0"
-    jest-validate "^26.4.2"
+    jest-resolve "^26.5.2"
+    jest-util "^26.5.2"
+    jest-validate "^26.5.2"
     micromatch "^4.0.2"
-    pretty-format "^26.4.2"
+    pretty-format "^26.5.2"
 
 jest-diff@^25.2.1:
   version "25.5.0"
@@ -8662,15 +8754,15 @@ jest-diff@^25.2.1:
     jest-get-type "^25.2.6"
     pretty-format "^25.5.0"
 
-jest-diff@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.4.2.tgz#a1b7b303bcc534aabdb3bd4a7caf594ac059f5aa"
-  integrity sha512-6T1XQY8U28WH0Z5rGpQ+VqZSZz8EN8rZcBtfvXaOkbwxIEeRre6qnuZQlbY1AJ4MKDxQF8EkrCvK+hL/VkyYLQ==
+jest-diff@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.5.2.tgz#8e26cb32dc598e8b8a1b9deff55316f8313c8053"
+  integrity sha512-HCSWDUGwsov5oTlGzrRM+UPJI/Dpqi9jzeV0fdRNi3Ch5bnoXhnyJMmVg2juv9081zLIy3HGPI5mcuGgXM2xRA==
   dependencies:
     chalk "^4.0.0"
-    diff-sequences "^26.3.0"
+    diff-sequences "^26.5.0"
     jest-get-type "^26.3.0"
-    pretty-format "^26.4.2"
+    pretty-format "^26.5.2"
 
 jest-docblock@^26.0.0:
   version "26.0.0"
@@ -8679,41 +8771,41 @@ jest-docblock@^26.0.0:
   dependencies:
     detect-newline "^3.0.0"
 
-jest-each@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.4.2.tgz#bb14f7f4304f2bb2e2b81f783f989449b8b6ffae"
-  integrity sha512-p15rt8r8cUcRY0Mvo1fpkOGYm7iI8S6ySxgIdfh3oOIv+gHwrHTy5VWCGOecWUhDsit4Nz8avJWdT07WLpbwDA==
+jest-each@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.5.2.tgz#35e68d6906a7f826d3ca5803cfe91d17a5a34c31"
+  integrity sha512-w7D9FNe0m2D3yZ0Drj9CLkyF/mGhmBSULMQTypzAKR746xXnjUrK8GUJdlLTWUF6dd0ks3MtvGP7/xNFr9Aphg==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     chalk "^4.0.0"
     jest-get-type "^26.3.0"
-    jest-util "^26.3.0"
-    pretty-format "^26.4.2"
+    jest-util "^26.5.2"
+    pretty-format "^26.5.2"
 
-jest-environment-jsdom@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.3.0.tgz#3b749ba0f3a78e92ba2c9ce519e16e5dd515220c"
-  integrity sha512-zra8He2btIMJkAzvLaiZ9QwEPGEetbxqmjEBQwhH3CA+Hhhu0jSiEJxnJMbX28TGUvPLxBt/zyaTLrOPF4yMJA==
+jest-environment-jsdom@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.5.2.tgz#5feab05b828fd3e4b96bee5e0493464ddd2bb4bc"
+  integrity sha512-fWZPx0bluJaTQ36+PmRpvUtUlUFlGGBNyGX1SN3dLUHHMcQ4WseNEzcGGKOw4U5towXgxI4qDoI3vwR18H0RTw==
   dependencies:
-    "@jest/environment" "^26.3.0"
-    "@jest/fake-timers" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/environment" "^26.5.2"
+    "@jest/fake-timers" "^26.5.2"
+    "@jest/types" "^26.5.2"
     "@types/node" "*"
-    jest-mock "^26.3.0"
-    jest-util "^26.3.0"
-    jsdom "^16.2.2"
-
-jest-environment-node@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.3.0.tgz#56c6cfb506d1597f94ee8d717072bda7228df849"
-  integrity sha512-c9BvYoo+FGcMj5FunbBgtBnbR5qk3uky8PKyRVpSfe2/8+LrNQMiXX53z6q2kY+j15SkjQCOSL/6LHnCPLVHNw==
-  dependencies:
-    "@jest/environment" "^26.3.0"
-    "@jest/fake-timers" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    jest-mock "^26.5.2"
+    jest-util "^26.5.2"
+    jsdom "^16.4.0"
+
+jest-environment-node@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.5.2.tgz#275a0f01b5e47447056f1541a15ed4da14acca03"
+  integrity sha512-YHjnDsf/GKFCYMGF1V+6HF7jhY1fcLfLNBDjhAOvFGvt6d8vXvNdJGVM7uTZ2VO/TuIyEFhPGaXMX5j3h7fsrA==
+  dependencies:
+    "@jest/environment" "^26.5.2"
+    "@jest/fake-timers" "^26.5.2"
+    "@jest/types" "^26.5.2"
     "@types/node" "*"
-    jest-mock "^26.3.0"
-    jest-util "^26.3.0"
+    jest-mock "^26.5.2"
+    jest-util "^26.5.2"
 
 jest-get-type@^25.2.6:
   version "25.2.6"
@@ -8725,89 +8817,89 @@ jest-get-type@^26.3.0:
   resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
   integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==
 
-jest-haste-map@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.3.0.tgz#c51a3b40100d53ab777bfdad382d2e7a00e5c726"
-  integrity sha512-DHWBpTJgJhLLGwE5Z1ZaqLTYqeODQIZpby0zMBsCU9iRFHYyhklYqP4EiG73j5dkbaAdSZhgB938mL51Q5LeZA==
+jest-haste-map@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.5.2.tgz#a15008abfc502c18aa56e4919ed8c96304ceb23d"
+  integrity sha512-lJIAVJN3gtO3k4xy+7i2Xjtwh8CfPcH08WYjZpe9xzveDaqGw9fVNCpkYu6M525wKFVkLmyi7ku+DxCAP1lyMA==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     "@types/graceful-fs" "^4.1.2"
     "@types/node" "*"
     anymatch "^3.0.3"
     fb-watchman "^2.0.0"
     graceful-fs "^4.2.4"
     jest-regex-util "^26.0.0"
-    jest-serializer "^26.3.0"
-    jest-util "^26.3.0"
-    jest-worker "^26.3.0"
+    jest-serializer "^26.5.0"
+    jest-util "^26.5.2"
+    jest-worker "^26.5.0"
     micromatch "^4.0.2"
     sane "^4.0.3"
     walker "^1.0.7"
   optionalDependencies:
     fsevents "^2.1.2"
 
-jest-jasmine2@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.4.2.tgz#18a9d5bec30904267ac5e9797570932aec1e2257"
-  integrity sha512-z7H4EpCldHN1J8fNgsja58QftxBSL+JcwZmaXIvV9WKIM+x49F4GLHu/+BQh2kzRKHAgaN/E82od+8rTOBPyPA==
+jest-jasmine2@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.5.2.tgz#0e33819d31b1f2aab5efd1e02ce502209c0e64a2"
+  integrity sha512-2J+GYcgLVPTkpmvHEj0/IDTIAuyblGNGlyGe4fLfDT2aktEPBYvoxUwFiOmDDxxzuuEAD2uxcYXr0+1Yw4tjFA==
   dependencies:
     "@babel/traverse" "^7.1.0"
-    "@jest/environment" "^26.3.0"
-    "@jest/source-map" "^26.3.0"
-    "@jest/test-result" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/environment" "^26.5.2"
+    "@jest/source-map" "^26.5.0"
+    "@jest/test-result" "^26.5.2"
+    "@jest/types" "^26.5.2"
     "@types/node" "*"
     chalk "^4.0.0"
     co "^4.6.0"
-    expect "^26.4.2"
+    expect "^26.5.2"
     is-generator-fn "^2.0.0"
-    jest-each "^26.4.2"
-    jest-matcher-utils "^26.4.2"
-    jest-message-util "^26.3.0"
-    jest-runtime "^26.4.2"
-    jest-snapshot "^26.4.2"
-    jest-util "^26.3.0"
-    pretty-format "^26.4.2"
+    jest-each "^26.5.2"
+    jest-matcher-utils "^26.5.2"
+    jest-message-util "^26.5.2"
+    jest-runtime "^26.5.2"
+    jest-snapshot "^26.5.2"
+    jest-util "^26.5.2"
+    pretty-format "^26.5.2"
     throat "^5.0.0"
 
-jest-leak-detector@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.4.2.tgz#c73e2fa8757bf905f6f66fb9e0070b70fa0f573f"
-  integrity sha512-akzGcxwxtE+9ZJZRW+M2o+nTNnmQZxrHJxX/HjgDaU5+PLmY1qnQPnMjgADPGCRPhB+Yawe1iij0REe+k/aHoA==
+jest-leak-detector@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.5.2.tgz#83fcf9a4a6ef157549552cb4f32ca1d6221eea69"
+  integrity sha512-h7ia3dLzBFItmYERaLPEtEKxy3YlcbcRSjj0XRNJgBEyODuu+3DM2o62kvIFvs3PsaYoIIv+e+nLRI61Dj1CNw==
   dependencies:
     jest-get-type "^26.3.0"
-    pretty-format "^26.4.2"
+    pretty-format "^26.5.2"
 
-jest-matcher-utils@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.4.2.tgz#fa81f3693f7cb67e5fc1537317525ef3b85f4b06"
-  integrity sha512-KcbNqWfWUG24R7tu9WcAOKKdiXiXCbMvQYT6iodZ9k1f7065k0keUOW6XpJMMvah+hTfqkhJhRXmA3r3zMAg0Q==
+jest-matcher-utils@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.5.2.tgz#6aa2c76ce8b9c33e66f8856ff3a52bab59e6c85a"
+  integrity sha512-W9GO9KBIC4gIArsNqDUKsLnhivaqf8MSs6ujO/JDcPIQrmY+aasewweXVET8KdrJ6ADQaUne5UzysvF/RR7JYA==
   dependencies:
     chalk "^4.0.0"
-    jest-diff "^26.4.2"
+    jest-diff "^26.5.2"
     jest-get-type "^26.3.0"
-    pretty-format "^26.4.2"
+    pretty-format "^26.5.2"
 
-jest-message-util@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.3.0.tgz#3bdb538af27bb417f2d4d16557606fd082d5841a"
-  integrity sha512-xIavRYqr4/otGOiLxLZGj3ieMmjcNE73Ui+LdSW/Y790j5acqCsAdDiLIbzHCZMpN07JOENRWX5DcU+OQ+TjTA==
+jest-message-util@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.5.2.tgz#6c4c4c46dcfbabb47cd1ba2f6351559729bc11bb"
+  integrity sha512-Ocp9UYZ5Jl15C5PNsoDiGEk14A4NG0zZKknpWdZGoMzJuGAkVt10e97tnEVMYpk7LnQHZOfuK2j/izLBMcuCZw==
   dependencies:
     "@babel/code-frame" "^7.0.0"
-    "@jest/types" "^26.3.0"
-    "@types/stack-utils" "^1.0.1"
+    "@jest/types" "^26.5.2"
+    "@types/stack-utils" "^2.0.0"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     micromatch "^4.0.2"
     slash "^3.0.0"
     stack-utils "^2.0.2"
 
-jest-mock@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.3.0.tgz#ee62207c3c5ebe5f35b760e1267fee19a1cfdeba"
-  integrity sha512-PeaRrg8Dc6mnS35gOo/CbZovoDPKAeB1FICZiuagAgGvbWdNNyjQjkOaGUa/3N3JtpQ/Mh9P4A2D4Fv51NnP8Q==
+jest-mock@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.5.2.tgz#c9302e8ef807f2bfc749ee52e65ad11166a1b6a1"
+  integrity sha512-9SiU4b5PtO51v0MtJwVRqeGEroH66Bnwtq4ARdNP7jNXbpT7+ByeWNAk4NeT/uHfNSVDXEXgQo1XRuwEqS6Rdw==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     "@types/node" "*"
 
 jest-pnp-resolver@^1.2.2:
@@ -8820,170 +8912,171 @@ jest-regex-util@^26.0.0:
   resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28"
   integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==
 
-jest-resolve-dependencies@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.4.2.tgz#739bdb027c14befb2fe5aabbd03f7bab355f1dc5"
-  integrity sha512-ADHaOwqEcVc71uTfySzSowA/RdxUpCxhxa2FNLiin9vWLB1uLPad3we+JSSROq5+SrL9iYPdZZF8bdKM7XABTQ==
+jest-resolve-dependencies@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.5.2.tgz#ee30b7cfea81c81bf5e195a9287d7ec07f893170"
+  integrity sha512-LLkc8LuRtxqOx0AtX/Npa2C4I23WcIrwUgNtHYXg4owYF/ZDQShcwBAHjYZIFR06+HpQcZ43+kCTMlQ3aDCYTg==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     jest-regex-util "^26.0.0"
-    jest-snapshot "^26.4.2"
+    jest-snapshot "^26.5.2"
 
-jest-resolve@^26.4.0:
-  version "26.4.0"
-  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.4.0.tgz#6dc0af7fb93e65b73fec0368ca2b76f3eb59a6d7"
-  integrity sha512-bn/JoZTEXRSlEx3+SfgZcJAVuTMOksYq9xe9O6s4Ekg84aKBObEaVXKOEilULRqviSLAYJldnoWV9c07kwtiCg==
+jest-resolve@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.5.2.tgz#0d719144f61944a428657b755a0e5c6af4fc8602"
+  integrity sha512-XsPxojXGRA0CoDD7Vis59ucz2p3cQFU5C+19tz3tLEAlhYKkK77IL0cjYjikY9wXnOaBeEdm1rOgSJjbZWpcZg==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     jest-pnp-resolver "^1.2.2"
-    jest-util "^26.3.0"
+    jest-util "^26.5.2"
     read-pkg-up "^7.0.1"
     resolve "^1.17.0"
     slash "^3.0.0"
 
-jest-runner@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.4.2.tgz#c3ec5482c8edd31973bd3935df5a449a45b5b853"
-  integrity sha512-FgjDHeVknDjw1gRAYaoUoShe1K3XUuFMkIaXbdhEys+1O4bEJS8Avmn4lBwoMfL8O5oFTdWYKcf3tEJyyYyk8g==
+jest-runner@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.5.2.tgz#4f9e6b0bb7eb4710c209a9e145b8a10894f4c19f"
+  integrity sha512-GKhYxtSX5+tXZsd2QwfkDqPIj5C2HqOdXLRc2x2qYqWE26OJh17xo58/fN/mLhRkO4y6o60ZVloan7Kk5YA6hg==
   dependencies:
-    "@jest/console" "^26.3.0"
-    "@jest/environment" "^26.3.0"
-    "@jest/test-result" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/console" "^26.5.2"
+    "@jest/environment" "^26.5.2"
+    "@jest/test-result" "^26.5.2"
+    "@jest/types" "^26.5.2"
     "@types/node" "*"
     chalk "^4.0.0"
     emittery "^0.7.1"
     exit "^0.1.2"
     graceful-fs "^4.2.4"
-    jest-config "^26.4.2"
+    jest-config "^26.5.2"
     jest-docblock "^26.0.0"
-    jest-haste-map "^26.3.0"
-    jest-leak-detector "^26.4.2"
-    jest-message-util "^26.3.0"
-    jest-resolve "^26.4.0"
-    jest-runtime "^26.4.2"
-    jest-util "^26.3.0"
-    jest-worker "^26.3.0"
+    jest-haste-map "^26.5.2"
+    jest-leak-detector "^26.5.2"
+    jest-message-util "^26.5.2"
+    jest-resolve "^26.5.2"
+    jest-runtime "^26.5.2"
+    jest-util "^26.5.2"
+    jest-worker "^26.5.0"
     source-map-support "^0.5.6"
     throat "^5.0.0"
 
-jest-runtime@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.4.2.tgz#94ce17890353c92e4206580c73a8f0c024c33c42"
-  integrity sha512-4Pe7Uk5a80FnbHwSOk7ojNCJvz3Ks2CNQWT5Z7MJo4tX0jb3V/LThKvD9tKPNVNyeMH98J/nzGlcwc00R2dSHQ==
-  dependencies:
-    "@jest/console" "^26.3.0"
-    "@jest/environment" "^26.3.0"
-    "@jest/fake-timers" "^26.3.0"
-    "@jest/globals" "^26.4.2"
-    "@jest/source-map" "^26.3.0"
-    "@jest/test-result" "^26.3.0"
-    "@jest/transform" "^26.3.0"
-    "@jest/types" "^26.3.0"
+jest-runtime@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.5.2.tgz#b72f5f79eb2fe0c46bfef4cdb9c1e01d1c69ba41"
+  integrity sha512-zArr4DatX/Sn0wswX/AnAuJgmwgAR5rNtrUz36HR8BfMuysHYNq5sDbYHuLC4ICyRdy5ae/KQ+sczxyS9G6Qvw==
+  dependencies:
+    "@jest/console" "^26.5.2"
+    "@jest/environment" "^26.5.2"
+    "@jest/fake-timers" "^26.5.2"
+    "@jest/globals" "^26.5.2"
+    "@jest/source-map" "^26.5.0"
+    "@jest/test-result" "^26.5.2"
+    "@jest/transform" "^26.5.2"
+    "@jest/types" "^26.5.2"
     "@types/yargs" "^15.0.0"
     chalk "^4.0.0"
     collect-v8-coverage "^1.0.0"
     exit "^0.1.2"
     glob "^7.1.3"
     graceful-fs "^4.2.4"
-    jest-config "^26.4.2"
-    jest-haste-map "^26.3.0"
-    jest-message-util "^26.3.0"
-    jest-mock "^26.3.0"
+    jest-config "^26.5.2"
+    jest-haste-map "^26.5.2"
+    jest-message-util "^26.5.2"
+    jest-mock "^26.5.2"
     jest-regex-util "^26.0.0"
-    jest-resolve "^26.4.0"
-    jest-snapshot "^26.4.2"
-    jest-util "^26.3.0"
-    jest-validate "^26.4.2"
+    jest-resolve "^26.5.2"
+    jest-snapshot "^26.5.2"
+    jest-util "^26.5.2"
+    jest-validate "^26.5.2"
     slash "^3.0.0"
     strip-bom "^4.0.0"
-    yargs "^15.3.1"
+    yargs "^15.4.1"
 
-jest-serializer@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.3.0.tgz#1c9d5e1b74d6e5f7e7f9627080fa205d976c33ef"
-  integrity sha512-IDRBQBLPlKa4flg77fqg0n/pH87tcRKwe8zxOVTWISxGpPHYkRZ1dXKyh04JOja7gppc60+soKVZ791mruVdow==
+jest-serializer@^26.5.0:
+  version "26.5.0"
+  resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.5.0.tgz#f5425cc4c5f6b4b355f854b5f0f23ec6b962bc13"
+  integrity sha512-+h3Gf5CDRlSLdgTv7y0vPIAoLgX/SI7T4v6hy+TEXMgYbv+ztzbg5PSN6mUXAT/hXYHvZRWm+MaObVfqkhCGxA==
   dependencies:
     "@types/node" "*"
     graceful-fs "^4.2.4"
 
-jest-snapshot@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.4.2.tgz#87d3ac2f2bd87ea8003602fbebd8fcb9e94104f6"
-  integrity sha512-N6Uub8FccKlf5SBFnL2Ri/xofbaA68Cc3MGjP/NuwgnsvWh+9hLIR/DhrxbSiKXMY9vUW5dI6EW1eHaDHqe9sg==
+jest-snapshot@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.5.2.tgz#0cf7642eaf8e8d2736bd443f619959bf237f9ccf"
+  integrity sha512-MkXIDvEefzDubI/WaDVSRH4xnkuirP/Pz8LhAIDXcVQTmcEfwxywj5LGwBmhz+kAAIldA7XM4l96vbpzltSjqg==
   dependencies:
     "@babel/types" "^7.0.0"
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
+    "@types/babel__traverse" "^7.0.4"
     "@types/prettier" "^2.0.0"
     chalk "^4.0.0"
-    expect "^26.4.2"
+    expect "^26.5.2"
     graceful-fs "^4.2.4"
-    jest-diff "^26.4.2"
+    jest-diff "^26.5.2"
     jest-get-type "^26.3.0"
-    jest-haste-map "^26.3.0"
-    jest-matcher-utils "^26.4.2"
-    jest-message-util "^26.3.0"
-    jest-resolve "^26.4.0"
+    jest-haste-map "^26.5.2"
+    jest-matcher-utils "^26.5.2"
+    jest-message-util "^26.5.2"
+    jest-resolve "^26.5.2"
     natural-compare "^1.4.0"
-    pretty-format "^26.4.2"
+    pretty-format "^26.5.2"
     semver "^7.3.2"
 
-jest-util@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.3.0.tgz#a8974b191df30e2bf523ebbfdbaeb8efca535b3e"
-  integrity sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw==
+jest-util@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.5.2.tgz#8403f75677902cc52a1b2140f568e91f8ed4f4d7"
+  integrity sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     "@types/node" "*"
     chalk "^4.0.0"
     graceful-fs "^4.2.4"
     is-ci "^2.0.0"
     micromatch "^4.0.2"
 
-jest-validate@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.4.2.tgz#e871b0dfe97747133014dcf6445ee8018398f39c"
-  integrity sha512-blft+xDX7XXghfhY0mrsBCYhX365n8K5wNDC4XAcNKqqjEzsRUSXP44m6PL0QJEW2crxQFLLztVnJ4j7oPlQrQ==
+jest-validate@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.5.2.tgz#7ea266700b64234cd1c0cee982490c5a80e9b0f0"
+  integrity sha512-FmJks0zY36mp6Af/5sqO6CTL9bNMU45yKCJk3hrz8d2aIqQIlN1pr9HPIwZE8blLaewOla134nt5+xAmWsx3SQ==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     camelcase "^6.0.0"
     chalk "^4.0.0"
     jest-get-type "^26.3.0"
     leven "^3.1.0"
-    pretty-format "^26.4.2"
+    pretty-format "^26.5.2"
 
-jest-watcher@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.3.0.tgz#f8ef3068ddb8af160ef868400318dc4a898eed08"
-  integrity sha512-XnLdKmyCGJ3VoF6G/p5ohbJ04q/vv5aH9ENI+i6BL0uu9WWB6Z7Z2lhQQk0d2AVZcRGp1yW+/TsoToMhBFPRdQ==
+jest-watcher@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.5.2.tgz#2957f4461007e0769d74b537379ecf6b7c696916"
+  integrity sha512-i3m1NtWzF+FXfJ3ljLBB/WQEp4uaNhX7QcQUWMokcifFTUQBDFyUMEwk0JkJ1kopHbx7Een3KX0Q7+9koGM/Pw==
   dependencies:
-    "@jest/test-result" "^26.3.0"
-    "@jest/types" "^26.3.0"
+    "@jest/test-result" "^26.5.2"
+    "@jest/types" "^26.5.2"
     "@types/node" "*"
     ansi-escapes "^4.2.1"
     chalk "^4.0.0"
-    jest-util "^26.3.0"
+    jest-util "^26.5.2"
     string-length "^4.0.1"
 
-jest-worker@^26.2.1, jest-worker@^26.3.0:
-  version "26.3.0"
-  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.3.0.tgz#7c8a97e4f4364b4f05ed8bca8ca0c24de091871f"
-  integrity sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==
+jest-worker@^26.5.0:
+  version "26.5.0"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.5.0.tgz#87deee86dbbc5f98d9919e0dadf2c40e3152fa30"
+  integrity sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==
   dependencies:
     "@types/node" "*"
     merge-stream "^2.0.0"
     supports-color "^7.0.0"
 
 jest@^26.1.0:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/jest/-/jest-26.4.2.tgz#7e8bfb348ec33f5459adeaffc1a25d5752d9d312"
-  integrity sha512-LLCjPrUh98Ik8CzW8LLVnSCfLaiY+wbK53U7VxnFSX7Q+kWC4noVeDvGWIFw0Amfq1lq2VfGm7YHWSLBV62MJw==
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/jest/-/jest-26.5.2.tgz#c6791642b331fe7abd2f993b0a74aa546f7be0fb"
+  integrity sha512-4HFabJVwsgDwul/7rhXJ3yFAF/aUkVIXiJWmgFxb+WMdZG39fVvOwYAs8/3r4AlFPc4m/n5sTMtuMbOL3kNtrQ==
   dependencies:
-    "@jest/core" "^26.4.2"
+    "@jest/core" "^26.5.2"
     import-local "^3.0.2"
-    jest-cli "^26.4.2"
+    jest-cli "^26.5.2"
 
 js-message@1.0.5:
   version "1.0.5"
@@ -9020,7 +9113,7 @@ jsbn@~0.1.0:
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
   integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
 
-jsdom@^16.2.2:
+jsdom@^16.4.0:
   version "16.4.0"
   resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb"
   integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==
@@ -9072,6 +9165,11 @@ json-buffer@3.0.0:
   resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
   integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
 
+json-buffer@3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
+  integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
 json-parse-better-errors@^1.0.1, 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"
@@ -9230,6 +9328,14 @@ jsx-ast-utils@^2.4.1:
     array-includes "^3.1.1"
     object.assign "^4.1.0"
 
+"jsx-ast-utils@^2.4.1 || ^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.0.0.tgz#0f49d5093bafa4b45d3fe02147d8b40ffc6c7438"
+  integrity sha512-sPuicm6EPKYI/UnWpOatvg4pI50qaBo4dSOMGUPutmJ26ttedFKXr0It0XXPk4HKnQ/1X0st4eSS2w2jhFk9Ow==
+  dependencies:
+    array-includes "^3.1.1"
+    object.assign "^4.1.1"
+
 jszip@^3.1.0:
   version "3.5.0"
   resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.5.0.tgz#b4fd1f368245346658e781fec9675802489e15f6"
@@ -9247,6 +9353,13 @@ keyv@^3.0.0:
   dependencies:
     json-buffer "3.0.0"
 
+keyv@^4.0.0:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254"
+  integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==
+  dependencies:
+    json-buffer "3.0.1"
+
 killable@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@@ -9281,10 +9394,10 @@ kleur@^3.0.3:
   resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
   integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
 
-klona@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/klona/-/klona-1.1.2.tgz#a79e292518a5a5412ec8d097964bff1571a64db0"
-  integrity sha512-xf88rTeHiXk+XE2Vhi6yj8Wm3gMZrygGdKjJqN8HkV+PwF/t50/LdAKHoHpPcxFAlmQszTZ1CugrK25S7qDRLA==
+klona@^2.0.4:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
+  integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
 
 known-css-properties@^0.19.0:
   version "0.19.0"
@@ -9471,11 +9584,6 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
-lodash.assign@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
-  integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=
-
 lodash.curry@^4.0.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
@@ -9531,13 +9639,6 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
   integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
 
-log-symbols@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
-  integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
-  dependencies:
-    chalk "^2.0.1"
-
 log-symbols@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
@@ -9611,6 +9712,16 @@ lru-cache@^6.0.0:
   dependencies:
     yallist "^4.0.0"
 
+lzma-native@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/lzma-native/-/lzma-native-6.0.1.tgz#eec231d31b9f9ba5aea5afc86326669f01dedb58"
+  integrity sha512-O6oWF0xe1AFvOCjU8uOZBZ/lhjaMNwHfVNaqVMqmoQXlRwBcFWpCAToiZOdXcKVMdo/5s/D0a2QgA5laMErxHQ==
+  dependencies:
+    node-addon-api "^1.6.0"
+    node-pre-gyp "^0.11.0"
+    readable-stream "^2.3.5"
+    rimraf "^2.7.1"
+
 make-dir@^2.0.0, make-dir@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
@@ -9854,12 +9965,17 @@ miller-rabin@^4.0.0:
     bn.js "^4.0.0"
     brorand "^1.0.1"
 
-mime-db@1.44.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.41.0:
+mime-db@1.44.0, "mime-db@>= 1.43.0 < 2":
   version "1.44.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
   integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
 
-mime-types@^2.1.12, mime-types@^2.1.26, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
+mime-db@^1.41.0:
+  version "1.45.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
+  integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
+
+mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
   version "2.1.27"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
   integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
@@ -9896,6 +10012,11 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
   resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
   integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
 
+mimic-response@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
+  integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+
 min-document@^2.19.0:
   version "2.19.0"
   resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
@@ -9916,14 +10037,14 @@ mini-create-react-context@^0.4.0:
     "@babel/runtime" "^7.5.5"
     tiny-warning "^1.0.3"
 
-mini-css-extract-plugin@^0.9.0:
-  version "0.9.0"
-  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e"
-  integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==
+mini-css-extract-plugin@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.0.0.tgz#4afb39f3d97b1b92eacb1ac45025416089f831bd"
+  integrity sha512-IsmrPv1nkdSUtFCDrAsuv5kg0k/27sLxfXqSz8vLjnbRKrNgoRdQrUNA4MppawvD+GHLkNP6L1P93Bw50ALkbg==
   dependencies:
-    loader-utils "^1.1.0"
+    loader-utils "^2.0.0"
     normalize-url "1.9.1"
-    schema-utils "^1.0.0"
+    schema-utils "^3.0.0"
     webpack-sources "^1.1.0"
 
 minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
@@ -10055,9 +10176,9 @@ moment-duration-format-commonjs@^1.0.0:
   integrity sha512-MVFR4hIh4jfuwSCPBEE5CCwn3refvTsxK/Yv/DpKJ6YcNnCimlVJ6DQeTJG1KVQPw1o8m3tkbHE9gVjivyv9iA==
 
 moment@^2.10.3, moment@^2.14.1:
-  version "2.28.0"
-  resolved "https://registry.yarnpkg.com/moment/-/moment-2.28.0.tgz#cdfe73ce01327cee6537b0fafac2e0f21a237d75"
-  integrity sha512-Z5KOjYmnHyd/ukynmFd/WwyXHd7L4J9vTI/nn5Ap9AVUgaAE15VvQ9MOGmJJygEUklupqIrFnor/tjTwRU+tQw==
+  version "2.29.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
+  integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
 
 moo@^0.5.0:
   version "0.5.1"
@@ -10109,6 +10230,11 @@ mustache@^2.1.1, mustache@^2.1.2, mustache@^2.2.1, mustache@^2.3.0:
   resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5"
   integrity sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==
 
+mute-stream@0.0.8:
+  version "0.0.8"
+  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
+  integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
+
 nan@^2.12.1:
   version "2.14.1"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
@@ -10169,6 +10295,15 @@ nearley@^2.7.10:
     randexp "0.4.6"
     semver "^5.4.1"
 
+needle@^2.2.1:
+  version "2.5.2"
+  resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.2.tgz#cf1a8fce382b5a280108bba90a14993c00e4010a"
+  integrity sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==
+  dependencies:
+    debug "^3.2.6"
+    iconv-lite "^0.4.4"
+    sax "^1.2.4"
+
 negotiator@0.6.2:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -10184,13 +10319,18 @@ nice-try@^1.0.4:
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
   integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
 
-node-abi@^2.11.0:
+node-abi@^2.19.1:
   version "2.19.1"
   resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.19.1.tgz#6aa32561d0a5e2fdb6810d8c25641b657a8cea85"
   integrity sha512-HbtmIuByq44yhAzK7b9j/FelKlHYISKQn0mtvcBrU5QBkhoCMp5bu8Hv5AI34DcKfOAcJBcOEMwLlwO62FFu9A==
   dependencies:
     semver "^5.4.1"
 
+node-addon-api@^1.6.0:
+  version "1.7.2"
+  resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d"
+  integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==
+
 node-fetch@^1.0.1:
   version "1.7.3"
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
@@ -10204,22 +10344,21 @@ node-forge@0.9.0:
   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
   integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==
 
-node-gyp@^6.0.1:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-6.1.0.tgz#64e31c61a4695ad304c1d5b82cf6b7c79cc79f3f"
-  integrity sha512-h4A2zDlOujeeaaTx06r4Vy+8MZ1679lU+wbCKDS4ZtvY2A37DESo37oejIw0mtmR3+rvNwts5B6Kpt1KrNYdNw==
+node-gyp@^7.1.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.0.tgz#cb8aed7ab772e73ad592ae0c71b0e3741099fe39"
+  integrity sha512-rjlHQlnl1dqiDZxZYiKqQdrjias7V+81OVR5PTzZioCBtWkNdrKy06M05HLKxy/pcKikKRCabeDRoZaEc6nIjw==
   dependencies:
     env-paths "^2.2.0"
     glob "^7.1.4"
-    graceful-fs "^4.2.2"
-    mkdirp "^0.5.1"
-    nopt "^4.0.1"
+    graceful-fs "^4.2.3"
+    nopt "^4.0.3"
     npmlog "^4.1.2"
-    request "^2.88.0"
+    request "^2.88.2"
     rimraf "^2.6.3"
-    semver "^5.7.1"
-    tar "^4.4.12"
-    which "^1.3.1"
+    semver "^7.3.2"
+    tar "^6.0.1"
+    which "^2.0.2"
 
 node-int64@^0.4.0:
   version "0.4.0"
@@ -10281,6 +10420,22 @@ node-notifier@^8.0.0:
     uuid "^8.3.0"
     which "^2.0.2"
 
+node-pre-gyp@^0.11.0:
+  version "0.11.0"
+  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054"
+  integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==
+  dependencies:
+    detect-libc "^1.0.2"
+    mkdirp "^0.5.1"
+    needle "^2.2.1"
+    nopt "^4.0.1"
+    npm-packlist "^1.1.6"
+    npmlog "^4.0.2"
+    rc "^1.2.7"
+    rimraf "^2.6.1"
+    semver "^5.3.0"
+    tar "^4"
+
 node-releases@^1.1.60, node-releases@^1.1.61:
   version "1.1.61"
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.61.tgz#707b0fca9ce4e11783612ba4a2fcba09047af16e"
@@ -10291,7 +10446,7 @@ node-version@^1.0.0:
   resolved "https://registry.yarnpkg.com/node-version/-/node-version-1.2.0.tgz#34fde3ffa8e1149bd323983479dda620e1b5060d"
   integrity sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ==
 
-nopt@^4.0.1:
+nopt@^4.0.1, nopt@^4.0.3:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
   integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==
@@ -10351,6 +10506,13 @@ normalize-url@^4.1.0:
   resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
   integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
 
+npm-bundled@^1.0.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b"
+  integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==
+  dependencies:
+    npm-normalize-package-bin "^1.0.1"
+
 npm-conf@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9"
@@ -10359,6 +10521,20 @@ npm-conf@^1.1.3:
     config-chain "^1.1.11"
     pify "^3.0.0"
 
+npm-normalize-package-bin@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2"
+  integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==
+
+npm-packlist@^1.1.6:
+  version "1.4.8"
+  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
+  integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
+  dependencies:
+    ignore-walk "^3.0.1"
+    npm-bundled "^1.0.1"
+    npm-normalize-package-bin "^1.0.1"
+
 npm-run-path@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -10373,7 +10549,7 @@ npm-run-path@^4.0.0:
   dependencies:
     path-key "^3.0.0"
 
-npmlog@^4.1.2:
+npmlog@^4.0.2, npmlog@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
   integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@@ -10429,7 +10605,7 @@ object-inspect@^1.7.0, object-inspect@^1.8.0:
   resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
   integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
 
-object-is@^1.0.1, object-is@^1.0.2, object-is@^1.1.2:
+object-is@^1.0.1, object-is@^1.0.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6"
   integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==
@@ -10437,6 +10613,14 @@ object-is@^1.0.1, object-is@^1.0.2, object-is@^1.1.2:
     define-properties "^1.1.3"
     es-abstract "^1.17.5"
 
+object-is@^1.1.2:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81"
+  integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg==
+  dependencies:
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.1"
+
 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"
@@ -10449,7 +10633,7 @@ object-visit@^1.0.0:
   dependencies:
     isobject "^3.0.0"
 
-object.assign@^4.1.0:
+object.assign@^4.1.0, object.assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd"
   integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==
@@ -10527,7 +10711,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
   dependencies:
     wrappy "1"
 
-onetime@^2.0.0, onetime@^2.0.1:
+onetime@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
   integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
@@ -10590,16 +10774,18 @@ optionator@^0.9.1:
     type-check "^0.4.0"
     word-wrap "^1.2.3"
 
-ora@^3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318"
-  integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==
+ora@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/ora/-/ora-5.1.0.tgz#b188cf8cd2d4d9b13fd25383bc3e5cba352c94f8"
+  integrity sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w==
   dependencies:
-    chalk "^2.4.2"
-    cli-cursor "^2.1.0"
-    cli-spinners "^2.0.0"
-    log-symbols "^2.2.0"
-    strip-ansi "^5.2.0"
+    chalk "^4.1.0"
+    cli-cursor "^3.1.0"
+    cli-spinners "^2.4.0"
+    is-interactive "^1.0.0"
+    log-symbols "^4.0.0"
+    mute-stream "0.0.8"
+    strip-ansi "^6.0.0"
     wcwidth "^1.0.1"
 
 original@^1.0.0:
@@ -10642,6 +10828,11 @@ p-cancelable@^1.0.0:
   resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
   integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
 
+p-cancelable@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e"
+  integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==
+
 p-each-series@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48"
@@ -11210,7 +11401,7 @@ postcss-modules-extract-imports@^2.0.0:
   dependencies:
     postcss "^7.0.5"
 
-postcss-modules-local-by-default@^3.0.2:
+postcss-modules-local-by-default@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0"
   integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==
@@ -11382,7 +11573,7 @@ postcss-selector-parser@^3.0.0:
     indexes-of "^1.0.1"
     uniq "^1.0.1"
 
-postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
+postcss-selector-parser@^6.0.0:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
   integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==
@@ -11391,6 +11582,16 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
     indexes-of "^1.0.1"
     uniq "^1.0.1"
 
+postcss-selector-parser@^6.0.2:
+  version "6.0.4"
+  resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3"
+  integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==
+  dependencies:
+    cssesc "^3.0.0"
+    indexes-of "^1.0.1"
+    uniq "^1.0.1"
+    util-deprecate "^1.0.2"
+
 postcss-svgo@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258"
@@ -11425,7 +11626,7 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
   integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
 
-postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.6:
+postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.27:
   version "7.0.34"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.34.tgz#f2baf57c36010df7de4009940f21532c16d65c20"
   integrity sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw==
@@ -11434,6 +11635,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.21
     source-map "^0.6.1"
     supports-color "^6.1.0"
 
+postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.6:
+  version "7.0.35"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24"
+  integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==
+  dependencies:
+    chalk "^2.4.2"
+    source-map "^0.6.1"
+    supports-color "^6.1.0"
+
 postcss@^7.0.5:
   version "7.0.32"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d"
@@ -11485,12 +11695,12 @@ pretty-format@^25.2.1, pretty-format@^25.5.0:
     ansi-styles "^4.0.0"
     react-is "^16.12.0"
 
-pretty-format@^26.4.2:
-  version "26.4.2"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.4.2.tgz#d081d032b398e801e2012af2df1214ef75a81237"
-  integrity sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==
+pretty-format@^26.5.2:
+  version "26.5.2"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.5.2.tgz#5d896acfdaa09210683d34b6dc0e6e21423cd3e1"
+  integrity sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==
   dependencies:
-    "@jest/types" "^26.3.0"
+    "@jest/types" "^26.5.2"
     ansi-regex "^5.0.0"
     ansi-styles "^4.0.0"
     react-is "^16.12.0"
@@ -11551,7 +11761,7 @@ prop-types-exact@^1.2.0:
     object.assign "^4.1.0"
     reflect.ownkeys "^0.2.0"
 
-prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
+prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
   version "15.7.2"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
   integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -11704,6 +11914,11 @@ quick-lru@^4.0.1:
   resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
   integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
 
+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.0, raf@^3.4.1:
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
@@ -11754,7 +11969,7 @@ raw-body@2.4.0:
     iconv-lite "0.4.24"
     unpipe "1.0.0"
 
-rc@^1.2.8:
+rc@^1.2.7, rc@^1.2.8:
   version "1.2.8"
   resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
   integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
@@ -11796,6 +12011,11 @@ react-dom@^16.12.0:
     prop-types "^15.6.2"
     scheduler "^0.19.1"
 
+react-fast-compare@^2.0.0:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
+  integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
+
 react-flame-graph@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/react-flame-graph/-/react-flame-graph-1.4.0.tgz#52d118cc94348f630a812fc0ec530a5b73c30cdb"
@@ -11806,9 +12026,9 @@ react-flame-graph@^1.4.0:
     react-window "^1"
 
 react-hot-loader@^4.12.21:
-  version "4.12.21"
-  resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.21.tgz#332e830801fb33024b5a147d6b13417f491eb975"
-  integrity sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA==
+  version "4.13.0"
+  resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.13.0.tgz#c27e9408581c2a678f5316e69c061b226dc6a202"
+  integrity sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA==
   dependencies:
     fast-levenshtein "^2.0.6"
     global "^4.3.0"
@@ -12036,7 +12256,7 @@ read-pkg@^5.2.0:
     parse-json "^5.0.0"
     type-fest "^0.6.0"
 
-"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
   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==
@@ -12351,7 +12571,7 @@ request-promise-native@^1.0.8:
     stealthy-require "^1.1.1"
     tough-cookie "^2.3.3"
 
-request@^2.88.0, request@^2.88.2:
+request@^2.88.2:
   version "2.88.2"
   resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
   integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -12397,6 +12617,11 @@ resize-observer-polyfill@^1.5.0:
   resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
   integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
 
+resolve-alpn@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c"
+  integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==
+
 resolve-cwd@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-1.0.0.tgz#4eaeea41ed040d1702457df64a42b2b07d246f9f"
@@ -12475,13 +12700,12 @@ responselike@^1.0.2:
   dependencies:
     lowercase-keys "^1.0.0"
 
-restore-cursor@^2.0.0:
+responselike@^2.0.0:
   version "2.0.0"
-  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
-  integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368=
+  resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723"
+  integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==
   dependencies:
-    onetime "^2.0.0"
-    signal-exit "^3.0.2"
+    lowercase-keys "^2.0.0"
 
 restore-cursor@^3.1.0:
   version "3.1.0"
@@ -12523,7 +12747,7 @@ rimraf@2.6.3:
   dependencies:
     glob "^7.1.3"
 
-rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
+rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3, rimraf@^2.7.1:
   version "2.7.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
   integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -12546,11 +12770,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
     inherits "^2.0.1"
 
 roarr@^2.15.3:
-  version "2.15.3"
-  resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.3.tgz#65248a291a15af3ebfd767cbf7e44cb402d1d836"
-  integrity sha512-AEjYvmAhlyxOeB9OqPUzQCo3kuAkNfuDk/HqWbZdFsqDFpapkTjiw+p4svNEoRLvuqNTxqfL+s+gtD4eDgZ+CA==
+  version "2.15.4"
+  resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd"
+  integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==
   dependencies:
-    boolean "^3.0.0"
+    boolean "^3.0.1"
     detect-node "^2.0.4"
     globalthis "^1.0.1"
     json-stringify-safe "^5.0.1"
@@ -12582,13 +12806,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
   dependencies:
     aproba "^1.1.1"
 
-rxjs@^6.3.1:
-  version "6.6.2"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2"
-  integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==
-  dependencies:
-    tslib "^1.9.0"
-
 rxjs@^6.5.2, rxjs@^6.6.2:
   version "6.6.3"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
@@ -12640,15 +12857,15 @@ sanitize-filename@^1.6.0, sanitize-filename@^1.6.2, sanitize-filename@^1.6.3:
   dependencies:
     truncate-utf8-bytes "^1.0.0"
 
-sass-loader@^9.0.2:
-  version "9.0.3"
-  resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-9.0.3.tgz#086adcf0bfdcc9d920413e2cdc3ba3321373d547"
-  integrity sha512-fOwsP98ac1VMme+V3+o0HaaMHp8Q/C9P+MUazLFVi3Jl7ORGHQXL1XeRZt3zLSGZQQPC8xE42Y2WptItvGjDQg==
+sass-loader@^10.0.3:
+  version "10.0.3"
+  resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.0.3.tgz#9e2f1bfdd6355f2adde4e4835d838b020bf800b0"
+  integrity sha512-W4+FV5oUdYy0PnC11ZoPrcAexODgDCa3ngxoy5X5qBhZYoPz9FPjb6Oox8Aa0ZYEyx34k8AQfOVuvqefOSAAUQ==
   dependencies:
-    klona "^1.1.2"
+    klona "^2.0.4"
     loader-utils "^2.0.0"
     neo-async "^2.6.2"
-    schema-utils "^2.7.0"
+    schema-utils "^3.0.0"
     semver "^7.3.2"
 
 sax@^1.2.4, sax@~1.2.4:
@@ -12689,14 +12906,14 @@ schema-utils@^2.6.5, schema-utils@^2.7.1:
     ajv "^6.12.4"
     ajv-keywords "^3.5.2"
 
-schema-utils@^2.6.6, 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==
+schema-utils@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
+  integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
   dependencies:
-    "@types/json-schema" "^7.0.4"
-    ajv "^6.12.2"
-    ajv-keywords "^3.4.1"
+    "@types/json-schema" "^7.0.6"
+    ajv "^6.12.5"
+    ajv-keywords "^3.5.2"
 
 select-hose@^2.0.0:
   version "2.0.0"
@@ -12785,6 +13002,13 @@ serialize-javascript@^4.0.0:
   dependencies:
     randombytes "^2.1.0"
 
+serialize-javascript@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
+  integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
+  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"
@@ -13040,7 +13264,7 @@ source-map-support@^0.4.15:
   dependencies:
     source-map "^0.5.6"
 
-source-map-support@^0.5.16, source-map-support@^0.5.17, source-map-support@^0.5.19, source-map-support@^0.5.6, source-map-support@~0.5.12:
+source-map-support@^0.5.16, source-map-support@^0.5.17, source-map-support@^0.5.19, source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19:
   version "0.5.19"
   resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
   integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
@@ -13070,7 +13294,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
-source-map@^0.7.3:
+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==
@@ -13080,15 +13304,6 @@ spawn-command@^0.0.2-1:
   resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
   integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=
 
-spawn-rx@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/spawn-rx/-/spawn-rx-3.0.0.tgz#1d33511e13ec26337da51d78630e08beb57a6767"
-  integrity sha512-dw4Ryg/KMNfkKa5ezAR5aZe9wNwPdKlnHEXtHOjVnyEDSPQyOpIPPRtcIiu7127SmtHhaCjw21yC43HliW0iIg==
-  dependencies:
-    debug "^2.5.1"
-    lodash.assign "^4.2.0"
-    rxjs "^6.3.1"
-
 spdx-correct@^3.0.0:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
@@ -13467,13 +13682,13 @@ strip-json-comments@~2.0.1:
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
 
-style-loader@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a"
-  integrity sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==
+style-loader@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c"
+  integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==
   dependencies:
     loader-utils "^2.0.0"
-    schema-utils "^2.6.6"
+    schema-utils "^3.0.0"
 
 style-search@^0.1.0:
   version "0.1.0"
@@ -13507,9 +13722,9 @@ stylelint-config-standard@^20.0.0:
     stylelint-config-recommended "^3.0.0"
 
 stylelint@^13.6.1:
-  version "13.7.1"
-  resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.1.tgz#bee97ee78d778a3f1dbe3f7397b76414973e263e"
-  integrity sha512-qzqazcyRxrSRdmFuO0/SZOJ+LyCxYy0pwcvaOBBnl8/2VfHSMrtNIE+AnyJoyq6uKb+mt+hlgmVrvVi6G6XHfQ==
+  version "13.7.2"
+  resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.2.tgz#6f3c58eea4077680ed0ceb0d064b22b100970486"
+  integrity sha512-mmieorkfmO+ZA6CNDu1ic9qpt4tFvH2QUB7vqXgrMVHe5ENU69q7YDq0YUg/UHLuCsZOWhUAvcMcLzLDIERzSg==
   dependencies:
     "@stylelint/postcss-css-in-js" "^0.37.2"
     "@stylelint/postcss-markdown" "^0.36.1"
@@ -13593,14 +13808,7 @@ supports-color@^6.1.0:
   dependencies:
     has-flag "^3.0.0"
 
-supports-color@^7.0.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
-  integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
-  dependencies:
-    has-flag "^4.0.0"
-
-supports-color@^7.1.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==
@@ -13674,7 +13882,7 @@ tapable@^1.0.0, tapable@^1.1.3:
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
   integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
 
-tar@^4.4.12:
+tar@^4:
   version "4.4.13"
   resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
   integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==
@@ -13687,7 +13895,7 @@ tar@^4.4.12:
     safe-buffer "^5.1.2"
     yallist "^3.0.3"
 
-tar@^6.0.2:
+tar@^6.0.1, tar@^6.0.2, tar@^6.0.5:
   version "6.0.5"
   resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f"
   integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==
@@ -13735,22 +13943,22 @@ terser-webpack-plugin@^1.4.3:
     webpack-sources "^1.4.0"
     worker-farm "^1.7.0"
 
-terser-webpack-plugin@^3.0.7:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-3.1.0.tgz#91e6d39571460ed240c0cf69d295bcf30ebf98cb"
-  integrity sha512-cjdZte66fYkZ65rQ2oJfrdCAkkhJA7YLYk5eGOcGCSGlq0ieZupRdjedSQXYknMPo2IveQL+tPdrxUkERENCFA==
+terser-webpack-plugin@^4.2.3:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a"
+  integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==
   dependencies:
     cacache "^15.0.5"
     find-cache-dir "^3.3.1"
-    jest-worker "^26.2.1"
+    jest-worker "^26.5.0"
     p-limit "^3.0.2"
-    schema-utils "^2.6.6"
-    serialize-javascript "^4.0.0"
+    schema-utils "^3.0.0"
+    serialize-javascript "^5.0.1"
     source-map "^0.6.1"
-    terser "^4.8.0"
+    terser "^5.3.4"
     webpack-sources "^1.4.3"
 
-terser@^4.1.2, terser@^4.8.0:
+terser@^4.1.2:
   version "4.8.0"
   resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
   integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
@@ -13759,6 +13967,15 @@ terser@^4.1.2, terser@^4.8.0:
     source-map "~0.6.1"
     source-map-support "~0.5.12"
 
+terser@^5.3.4:
+  version "5.3.4"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.4.tgz#e510e05f86e0bd87f01835c3238839193f77a60c"
+  integrity sha512-dxuB8KQo8Gt6OVOeLg/rxfcxdNZI/V1G6ze1czFUzPeCFWZRtvZMgSzlZZ5OYBZ4HoG607F6pFPNLekJyV+yVw==
+  dependencies:
+    commander "^2.20.0"
+    source-map "~0.7.2"
+    source-map-support "~0.5.19"
+
 test-exclude@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
@@ -13808,10 +14025,10 @@ testcafe-browser-tools@2.0.13:
     read-file-relative "^1.2.0"
     which-promise "^1.0.0"
 
-testcafe-hammerhead@17.1.18:
-  version "17.1.18"
-  resolved "https://registry.yarnpkg.com/testcafe-hammerhead/-/testcafe-hammerhead-17.1.18.tgz#1de65d6f63f7037a150f0987826f1f528df91c71"
-  integrity sha512-mrIMd1NsX+aRH37wb6tlGZoQMttXw0cwJn23iI4Y00oD8VwJfBz0x01Df1HVSuodjTqCEKdJHvDnyjAAsGjaAw==
+testcafe-hammerhead@17.1.20:
+  version "17.1.20"
+  resolved "https://registry.yarnpkg.com/testcafe-hammerhead/-/testcafe-hammerhead-17.1.20.tgz#eed2eeb938ad1ca5e7a41c55e3e6e135bf599df2"
+  integrity sha512-1Z1r+cUjn0Cg3/Q8uwUYGeL+UvKPXAtYtiOqdGKM8/nXxndxc+q3KBvmBZXSUE9rAWhn3Mb3SQ8LoSw1+iQyiA==
   dependencies:
     acorn-hammerhead "^0.3.0"
     asar "^2.0.1"
@@ -13888,9 +14105,9 @@ testcafe-reporter-xunit@^2.1.0:
   integrity sha1-5tZsVyzhWvJmcGrw/WELKoQd1EM=
 
 testcafe@^1.8.8:
-  version "1.9.3"
-  resolved "https://registry.yarnpkg.com/testcafe/-/testcafe-1.9.3.tgz#049b5d10127ce83993a46e35f429696ce774770b"
-  integrity sha512-1L5c8lHitGz/0Ta6AtA+J4e+YveV+XeoaY06mBWuVnijp56zJG4Xz1O2XNbMgprfLj7oNl96DW/MjJyW9B8QzA==
+  version "1.9.4"
+  resolved "https://registry.yarnpkg.com/testcafe/-/testcafe-1.9.4.tgz#a9fb9981e15cbbd559a93429adad527259921a9c"
+  integrity sha512-tiG0a47vvwuI6xle002wQitpEGG3Oi+qJOjFAgXT7rPYzcHrPRYP2rgefvCw/VE/qsGRHzyvDDYH3FbQMojDAA==
   dependencies:
     "@types/node" "^10.12.19"
     async-exit-hook "^1.1.2"
@@ -13957,7 +14174,7 @@ testcafe@^1.8.8:
     source-map-support "^0.5.16"
     strip-bom "^2.0.0"
     testcafe-browser-tools "2.0.13"
-    testcafe-hammerhead "17.1.18"
+    testcafe-hammerhead "17.1.20"
     testcafe-legacy-api "4.0.0"
     testcafe-reporter-json "^2.1.0"
     testcafe-reporter-list "^2.1.0"
@@ -14207,7 +14424,12 @@ tsconfig-paths@^3.9.0:
     minimist "^1.2.0"
     strip-bom "^3.0.0"
 
-tslib@^1.8.1, tslib@^1.9.0:
+tslib@^1.8.1:
+  version "1.14.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.0.tgz#d624983f3e2c5e0b55307c3dd6c86acd737622c6"
+  integrity sha512-+Zw5lu0D9tvBMjGP8LpvMb0u2WW2QV3y+D8mO6J+cNzCYIN4sVy43Bf9vl92nqFahutN0I8zHa7cc4vihIshnw==
+
+tslib@^1.9.0:
   version "1.13.0"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
   integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
@@ -14305,11 +14527,16 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-typescript@^3.3.3, typescript@^3.9.7:
+typescript@^3.3.3:
   version "3.9.7"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
   integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
 
+typescript@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5"
+  integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==
+
 typings-for-css-modules-loader@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/typings-for-css-modules-loader/-/typings-for-css-modules-loader-1.7.0.tgz#a9b5c5a0e19b719d616edfc72855ab47dedd00ae"
@@ -14530,13 +14757,13 @@ urix@^0.1.0:
   integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
 
 url-loader@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.0.tgz#c7d6b0d6b0fccd51ab3ffc58a78d32b8d89a7be2"
-  integrity sha512-IzgAAIC8wRrg6NYkFIJY09vtktQcsvU8V6HhtQj9PTefbYImzLB1hufqo4m+RyM5N3mLx5BqJKccgxJS+W3kqw==
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2"
+  integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==
   dependencies:
     loader-utils "^2.0.0"
-    mime-types "^2.1.26"
-    schema-utils "^2.6.5"
+    mime-types "^2.1.27"
+    schema-utils "^3.0.0"
 
 url-parse-lax@^3.0.0:
   version "3.0.0"
@@ -14580,7 +14807,7 @@ utf8-byte-length@^1.0.1:
   resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61"
   integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=
 
-util-deprecate@^1.0.1, util-deprecate@~1.0.1:
+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=
@@ -14620,9 +14847,9 @@ uuid@^3.3.2, uuid@^3.4.0:
   integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
 
 uuid@^8.3.0:
-  version "8.3.0"
-  resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"
-  integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==
+  version "8.3.1"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
+  integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
 
 v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1:
   version "2.1.1"
@@ -14694,6 +14921,306 @@ vfile@^4.0.0:
     unist-util-stringify-position "^2.0.0"
     vfile-message "^2.0.0"
 
+victory-area@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-area/-/victory-area-35.3.0.tgz#e678091a1458833c9744038e236693b31bcd70a4"
+  integrity sha512-9828chzvj2O+lr+YW6m43wwzcHMr17h80yEAhQ2XOmrVQhUt/FNLY6RNMnhI0GYNN4DJgiSfCG5BEHMtbl2Sjg==
+  dependencies:
+    d3-shape "^1.2.0"
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-axis@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-axis/-/victory-axis-35.3.0.tgz#d8008ae890d7392c66907292e3f39fcb0ce6c187"
+  integrity sha512-jg+NYWIAQZd1OaIzf9vTlVOj75OxfiTR1noSIf/SsssVsT3BUlYooptPhW1mZX4lihgPbDJymX2FK5VsBPORbA==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-bar@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-bar/-/victory-bar-35.3.0.tgz#8281d16661f1f76995cdc8817f8b74fb5ed1987a"
+  integrity sha512-FR9vVdLvqGRQ882CXf4tD02zPc8GnLYCl+MB0w0okuA1xVjBXPqK6pvQ88fueyA4hGT64lZYIi1NkI9wgo7WfQ==
+  dependencies:
+    d3-shape "^1.2.0"
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-box-plot@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-box-plot/-/victory-box-plot-35.3.0.tgz#94ac2221f9d23c2f223ce13791d379500d5654c4"
+  integrity sha512-v1Ob7/GT0+jRL75DGzhMfUjeVZosFDE7Ptf9ncPqAkRCaXQ8ZiHJRDMcGqmsgtnrlsQqjLG+Y1WP985bujvOqA==
+  dependencies:
+    d3-array "^1.2.0"
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-brush-container@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-brush-container/-/victory-brush-container-35.3.0.tgz#06cc9518339aec738a7c52d0768e914edf50ea5c"
+  integrity sha512-PKYUpcjmL+M0W9LTfRF7/MXKWbIhFdA6eJ3U7aKupEuiY9gOJxjwVziwN4aNtHcFWwXJQTkqOWD6PkKigM6ZaA==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    react-fast-compare "^2.0.0"
+    victory-core "^35.3.0"
+
+victory-brush-line@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-brush-line/-/victory-brush-line-35.3.0.tgz#057a8ed0ce6ce406b87b05764f2cbd73a4e0f1df"
+  integrity sha512-HLpWTmEizsHwhOtwp+w0J34EGrXcl8H+IJ0MMMjiH0RJLyN3Bd8Rm4Y6be8gbfRDdOmbVVuFAye8AegOoACDwA==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    react-fast-compare "^2.0.0"
+    victory-core "^35.3.0"
+
+victory-candlestick@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-candlestick/-/victory-candlestick-35.3.0.tgz#ead10bc620b654cd06448f602028ca2dd6066e76"
+  integrity sha512-8nVfKxo51iwUmA2B2YiTybZSv1XQkFCrf6mdAz1cFGoDFGPEJFTNJ0fEE3p1bvVjH/RyAEB6g6lEHXTeJsCb0g==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-chart@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-chart/-/victory-chart-35.3.0.tgz#db9b231c15deec4d3e99b440716d4a91718385bf"
+  integrity sha512-+hMVyOfehH0pkEucK0Shw7/w27UkZtsrmMscNgI7kGT/rRDgILLsCc6PuSNQbFUDA+xm6wetz+fOd0VTSvuMYg==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    react-fast-compare "^2.0.0"
+    victory-axis "^35.3.0"
+    victory-core "^35.3.0"
+    victory-polar-axis "^35.3.0"
+    victory-shared-events "^35.3.0"
+
+victory-core@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-core/-/victory-core-35.3.0.tgz#d2578fc9044a1d82b4684d749c9573142a6ad06e"
+  integrity sha512-zurfbY6D/1sQxRhDpVItPGiiRxjySu2Dgm09kS9lNihIBE03pTEc+PBWnJZzUlDRX3leqh7eYV3OZbx0oXlmqA==
+  dependencies:
+    d3-ease "^1.0.0"
+    d3-interpolate "^1.1.1"
+    d3-scale "^1.0.0"
+    d3-shape "^1.2.0"
+    d3-timer "^1.0.0"
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    react-fast-compare "^2.0.0"
+
+victory-create-container@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-create-container/-/victory-create-container-35.3.0.tgz#6e98d6eb0e4673c707834a2290ea549584bb6ba9"
+  integrity sha512-RZcKJsj897KSZIIJhnkCMfpnwmYu5AsbePp0KZ8bQOtmZi75mqtsvehi5vQfNa0QjLyMBOx8J7EC7+3h5m+j2g==
+  dependencies:
+    lodash "^4.17.19"
+    victory-brush-container "^35.3.0"
+    victory-core "^35.3.0"
+    victory-cursor-container "^35.3.0"
+    victory-selection-container "^35.3.0"
+    victory-voronoi-container "^35.3.0"
+    victory-zoom-container "^35.3.0"
+
+victory-cursor-container@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-cursor-container/-/victory-cursor-container-35.3.0.tgz#4931c2b3d5d4f081a394abd586dc969764f6e740"
+  integrity sha512-zmWAAZwPrBP+FAS8A5p7BmEjeM82hvxbUpaCg9EgmRzTqfl5wPBf+gEsBc21tuneJ/5gzIsTzA5ueklBQ0rBMg==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-errorbar@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-errorbar/-/victory-errorbar-35.3.0.tgz#b6d440696ca5e28bc7f5fe8818d1c7eff344677f"
+  integrity sha512-gDx4m8HqlzmTmRDkGFl4ZUYFIy1zWE9AgC7ZAiAFy0J4VnAnoVVScALLjQO2nAu3NpVGFPikDCN0cLTHYP/c/Q==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-group@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-group/-/victory-group-35.3.0.tgz#df3de12058efbd1125f1378498f2ec994a3a8667"
+  integrity sha512-aulnBC9pb7xm/sNs15elTNSFBBnPBDw0xvYYggAyudvgHEHJSbmRefICIPUtTLKsIaNzjpOjZkJX1n15o2R5rg==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    react-fast-compare "^2.0.0"
+    victory-core "^35.3.0"
+    victory-shared-events "^35.3.0"
+
+victory-histogram@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-histogram/-/victory-histogram-35.3.0.tgz#23fb3ab15d2f0988cc99a13eed6e2311ccd50408"
+  integrity sha512-52yq28CpA1jbXdw5mPPM94audMagW4gnN0Hwz4Tkb3EXnZJkV/wdKstrH7O/Rcgqhbj/q2scqnRm/T+f7sSeEQ==
+  dependencies:
+    d3-array "^2.4.0"
+    d3-scale "^1.0.0"
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    react-fast-compare "^2.0.0"
+    victory-bar "^35.3.0"
+    victory-core "^35.3.0"
+
+victory-legend@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-legend/-/victory-legend-35.3.0.tgz#295eacc895b495fec4b34ff2c6b0b84fb8db2d90"
+  integrity sha512-TBNvURpEDug2+qavn5sQnLohxH3IY6GBtFKUXXBnvuhx3Z/AWKl34PbJpEzpmyhzUQjS9o+YTHH/IxFZZIsycg==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-line@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-line/-/victory-line-35.3.0.tgz#61c48ef5161ebcf8e4409a35d56b0fe90c258708"
+  integrity sha512-umLkeF9E7fLZ0iXQAUCm6FS1HTWJTHWaqunoI4zeHKSM8ub4vQA3cjk9vfWFE4WQsDL5Hg6QQhMfqQFh+NxkVg==
+  dependencies:
+    d3-shape "^1.2.0"
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-pie@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-pie/-/victory-pie-35.3.0.tgz#98d8ca2507690f72bcd279303e4018d2c8f753e4"
+  integrity sha512-Um6Giohc02EcXM0B7nVo9/owAJph3qMcHDmMl8jZSkWgl7jdMpe74A8o3a5pUBxoScDfefjJqKSv6qsPOsQ3TQ==
+  dependencies:
+    d3-shape "^1.0.0"
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-polar-axis@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-polar-axis/-/victory-polar-axis-35.3.0.tgz#806da0dd81c675d4bc6d1b505253ae2799a09e74"
+  integrity sha512-xX9+jU8tIesOMBm6m9Z3Z9IpIi/ya+fd9FSslZ4HNytEfjoheM84RffqIWHWlXE6kq9WwF5CmsPad3FpQ52kmg==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-scatter@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-scatter/-/victory-scatter-35.3.0.tgz#d92245c48f46739f2300df3e16ae979c9aa1c89e"
+  integrity sha512-Yd05kR+h9V16i0fSe39x5mwcH9Zxf2wiFQKWS2Riehm78VSiSQVEAEHepiX56BmjcgBAkllWzCZ4ozkFDTdlSg==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-selection-container@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-selection-container/-/victory-selection-container-35.3.0.tgz#499af55473210458e5f3d1bffcae9c262fcd8e61"
+  integrity sha512-BjiZ2wB45gxMOX37IJ0/fx5OxT4vyGVSix6SKNzV31htNROxFJBcRSS4rbh4f2g1vu/FSkrxl5DWJLkEWFemBA==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-shared-events@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-shared-events/-/victory-shared-events-35.3.0.tgz#35362903b0ef9afdd6ca5da722463ba583774fb9"
+  integrity sha512-DXH7g2xyv+IM3NHvJNhGj+Ugu0FLLpP4pm61u+Sie5gXuZ3EazgIgeU9KyyTKeghL3hIW4T0KG8FTalv2BYVyQ==
+  dependencies:
+    json-stringify-safe "^5.0.1"
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    react-fast-compare "^2.0.0"
+    victory-core "^35.3.0"
+
+victory-stack@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-stack/-/victory-stack-35.3.0.tgz#c77a19a7111352a3cf236838e3b7dec40a076213"
+  integrity sha512-6MIH5803saE8dDPgxtyDvrsaOSsjLpWW+jrzU/NJWziFnCyVDRErdLYHSRSAJcbpZeNrYAra5E0FEllyB2baFA==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    react-fast-compare "^2.0.0"
+    victory-core "^35.3.0"
+    victory-shared-events "^35.3.0"
+
+victory-tooltip@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-tooltip/-/victory-tooltip-35.3.0.tgz#cace6779c061e4f2677c9854dc8c65dac53b3277"
+  integrity sha512-t490Wyjm61JCQJlystnROEwioYyO+VX6ZR1T49YJzW5IBekJLLUXQdOgUGZvLuv3EW6xzeEkueBOP3v6nJ13wQ==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-voronoi-container@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-voronoi-container/-/victory-voronoi-container-35.3.0.tgz#ee1ceb699db0119ee7e623ab273ca3af5a456585"
+  integrity sha512-U8tDlmk+EOp6VALzdcHyF8ZGKp+0Wh27TOzUP9JOpcHCFxqOLAn+Prgp8FmOqMCBnv//014MKi1gCI7Bxk6J8Q==
+  dependencies:
+    delaunay-find "0.0.5"
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    react-fast-compare "^2.0.0"
+    victory-core "^35.3.0"
+    victory-tooltip "^35.3.0"
+
+victory-voronoi@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-voronoi/-/victory-voronoi-35.3.0.tgz#52f98bde02cce96baa91f1c9b93bf26879399421"
+  integrity sha512-RvyY4++KaqZd+7CRtnc7N9C/tGi2i6barGmMQ+Ccw4xtzrZNtv5+ErCXC7DInXmgvMoXOVrAXAoDZaUzoRzN3g==
+  dependencies:
+    d3-voronoi "^1.1.2"
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory-zoom-container@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory-zoom-container/-/victory-zoom-container-35.3.0.tgz#a41f4e7aac47d51a9ccab6b0e12e5cef6d4661aa"
+  integrity sha512-1mmiFUodStPOD8xZtn67wnN5d7NE4bJ0bRXezWIEHw/7FKcUyjGFEH0qpcFOwiHKxeyfYqkiC4smx3AuRAHESg==
+  dependencies:
+    lodash "^4.17.19"
+    prop-types "^15.5.8"
+    victory-core "^35.3.0"
+
+victory@^35.3.0:
+  version "35.3.0"
+  resolved "https://registry.yarnpkg.com/victory/-/victory-35.3.0.tgz#98040c0aada32c90619867c42591cdc0f2b7c0bf"
+  integrity sha512-78RmzOLEtufnHEAOcFi3YiBryCwOAvd6zw6st+Tn7HMbDHeVSSd0Whgrk3EOpPU68mdrtLgf4XZH1mkcf2dVtw==
+  dependencies:
+    victory-area "^35.3.0"
+    victory-axis "^35.3.0"
+    victory-bar "^35.3.0"
+    victory-box-plot "^35.3.0"
+    victory-brush-container "^35.3.0"
+    victory-brush-line "^35.3.0"
+    victory-candlestick "^35.3.0"
+    victory-chart "^35.3.0"
+    victory-core "^35.3.0"
+    victory-create-container "^35.3.0"
+    victory-cursor-container "^35.3.0"
+    victory-errorbar "^35.3.0"
+    victory-group "^35.3.0"
+    victory-histogram "^35.3.0"
+    victory-legend "^35.3.0"
+    victory-line "^35.3.0"
+    victory-pie "^35.3.0"
+    victory-polar-axis "^35.3.0"
+    victory-scatter "^35.3.0"
+    victory-selection-container "^35.3.0"
+    victory-shared-events "^35.3.0"
+    victory-stack "^35.3.0"
+    victory-tooltip "^35.3.0"
+    victory-voronoi "^35.3.0"
+    victory-voronoi-container "^35.3.0"
+    victory-zoom-container "^35.3.0"
+
 vm-browserify@^1.0.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
@@ -14862,9 +15389,9 @@ webpack-log@^2.0.0:
     uuid "^3.3.2"
 
 webpack-merge@^5.1.4:
-  version "5.1.4"
-  resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.1.4.tgz#a2c3a0c38ac2c02055c47bb1d42de1f072f1aea4"
-  integrity sha512-LSmRD59mxREGkCBm9PCW3AaV4doDqxykGlx1NvioEE0FgkT2GQI54Wyvg39ptkiq2T11eRVoV39udNPsQvK+QQ==
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.2.0.tgz#31cbcc954f8f89cd4b06ca8d97a38549f7f3f0c9"
+  integrity sha512-QBglJBg5+lItm3/Lopv8KDDK01+hjdg2azEwi/4vKJ8ZmGPdtJsTpjtNNOW3a4WiqzXdCATtTudOZJngE7RKkA==
   dependencies:
     clone-deep "^4.0.1"
     wildcard "^2.0.0"
@@ -14945,9 +15472,9 @@ whatwg-mimetype@^2.3.0:
   integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
 
 whatwg-url@^8.0.0:
-  version "8.2.1"
-  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.2.1.tgz#ed73417230784b281fb2a32c3c501738b46167c3"
-  integrity sha512-ZmVCr6nfBeaMxEHALLEGy0LszYjpJqf6PVNQUQ1qd9Et+q7Jpygd4rGGDXgHjD8e99yLFseD69msHDM4YwPZ4A==
+  version "8.4.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.4.0.tgz#50fb9615b05469591d2b2bd6dfaed2942ed72837"
+  integrity sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==
   dependencies:
     lodash.sortby "^4.7.0"
     tr46 "^2.0.2"
@@ -15043,6 +15570,15 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
+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"
@@ -15111,6 +15647,11 @@ y18n@^4.0.0:
   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
   integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
 
+y18n@^5.0.1:
+  version "5.0.2"
+  resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.2.tgz#48218df5da2731b4403115c39a1af709c873f829"
+  integrity sha512-CkwaeZw6dQgqgPGeTWKMXCRmMcBgETFlTml1+ZOO+q7kGst8NREJ+eWwFNPVUQ4QGdAaklbqCZHH6Zuep1RjiA==
+
 yaku@^0.16.6:
   version "0.16.7"
   resolved "https://registry.yarnpkg.com/yaku/-/yaku-0.16.7.tgz#1d195c78aa9b5bf8479c895b9504fd4f0847984e"
@@ -15139,14 +15680,6 @@ yargs-parser@^13.1.2:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
-yargs-parser@^15.0.1:
-  version "15.0.1"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.1.tgz#54786af40b820dcb2fb8025b11b4d659d76323b3"
-  integrity sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==
-  dependencies:
-    camelcase "^5.0.0"
-    decamelize "^1.2.0"
-
 yargs-parser@^18.1.2, yargs-parser@^18.1.3:
   version "18.1.3"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
@@ -15155,6 +15688,11 @@ yargs-parser@^18.1.2, yargs-parser@^18.1.3:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
+yargs-parser@^20.0.0:
+  version "20.2.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.1.tgz#28f3773c546cdd8a69ddae68116b48a5da328e77"
+  integrity sha512-yYsjuSkjbLMBp16eaOt7/siKTjNVjMm3SoJnIg3sEh/JsvqVVDyjRKmaJV4cl+lNIgq6QEco2i3gDebJl7/vLA==
+
 yargs@^13.3.0, yargs@^13.3.2:
   version "13.3.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
@@ -15171,24 +15709,7 @@ yargs@^13.3.0, yargs@^13.3.2:
     y18n "^4.0.0"
     yargs-parser "^13.1.2"
 
-yargs@^14.2.0:
-  version "14.2.3"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.3.tgz#1a1c3edced1afb2a2fea33604bc6d1d8d688a414"
-  integrity sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==
-  dependencies:
-    cliui "^5.0.0"
-    decamelize "^1.2.0"
-    find-up "^3.0.0"
-    get-caller-file "^2.0.1"
-    require-directory "^2.1.1"
-    require-main-filename "^2.0.0"
-    set-blocking "^2.0.0"
-    string-width "^3.0.0"
-    which-module "^2.0.0"
-    y18n "^4.0.0"
-    yargs-parser "^15.0.1"
-
-yargs@^15.3.1, yargs@^15.4.1:
+yargs@^15.4.1:
   version "15.4.1"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
   integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
@@ -15205,6 +15726,19 @@ yargs@^15.3.1, yargs@^15.4.1:
     y18n "^4.0.0"
     yargs-parser "^18.1.2"
 
+yargs@^16.0.0:
+  version "16.0.3"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c"
+  integrity sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA==
+  dependencies:
+    cliui "^7.0.0"
+    escalade "^3.0.2"
+    get-caller-file "^2.0.5"
+    require-directory "^2.1.1"
+    string-width "^4.2.0"
+    y18n "^5.0.1"
+    yargs-parser "^20.0.0"
+
 yarn@^1.22.10:
   version "1.22.10"
   resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.10.tgz#c99daa06257c80f8fa2c3f1490724e394c26b18c"