diff --git a/README.md b/README.md deleted file mode 100644 index a337f0886..000000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# justice-counts -Technical infrastructure for the Justice Counts initiative diff --git a/publisher/README.md b/publisher/README.md new file mode 100644 index 000000000..7c3bc7421 --- /dev/null +++ b/publisher/README.md @@ -0,0 +1,35 @@ +<<<<<<< HEAD +# justice-counts +Technical infrastructure for the Justice Counts initiative +======= +# Justice Counts Control Panel: Frontend + +Welcome to the Justice Counts Control Panel - a tool that allows agencies to report Justice Counts metrics. + +The frontend of this application, which lives in this directory, was bootstrapped with [Create React App](https://github.com/facebook/create-react-app), written in [TypeScript](https://www.typescriptlang.org/docs), and authenticated via [Auth0](https://auth0.com/). The backend of the application lives in [recidiviz-data/justice_counts/control_panel](https://github.com/Recidiviz/recidiviz-data/tree/main/recidiviz/justice_counts/control_panel). + +To run the app locally, you need to spin up both the backend and frontend simultaneously. Instructions for spinning up the frontend are below; instructions for spinning up the backend can be found in its directory's README. + +## Running the app frontend + +1. Install dependencies + + ##### For all Yarn installation options, see [Yarn Installation](https://yarnpkg.com/en/docs/install). + + ```sh + yarn install + ``` + +2. Run the local development server + + ```sh + yarn run dev + ``` + +3. Test your development environment + + ```sh + yarn test + yarn lint + ``` +>>>>>>> 47b3a8316fb088dc90672a36eefb3e8feb4385c1 diff --git a/publisher/package.json b/publisher/package.json new file mode 100644 index 000000000..889383524 --- /dev/null +++ b/publisher/package.json @@ -0,0 +1,103 @@ +{ + "name": "recidiviz-justice-counts-control-panel", + "version": "0.1.0", + "description": "A tool that allows agencies to report Justice Counts metrics", + "private": true, + "author": "Recidiviz ", + "license": "GNU General Public License v3", + "dependencies": { + "@auth0/auth0-react": "^1.9.0", + "@recidiviz/design-system": "^2.7.0", + "@testing-library/dom": "^8.11.3", + "@testing-library/jest-dom": "^5.14.1", + "@testing-library/react": "^12.0.0", + "@testing-library/user-event": "^13.2.1", + "lodash.debounce": "^4.0.8", + "lodash.mapvalues": "^4.6.0", + "lodash.pickby": "^4.6.0", + "mobx": "^6.4.2", + "mobx-persist-store": "^1.0.6", + "mobx-react-lite": "^3.3.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-is": "^17.0.2", + "react-router-dom": "^6", + "react-scripts": "5.0.0", + "recharts": "^2.1.13", + "styled-components": "^5.3.3", + "typescript": "^4.4.2", + "web-vitals": "^2.1.0" + }, + "scripts": { + "dev": "react-scripts start", + "build": "react-scripts build", + "lint": "tsc && eslint '**/*.{ts,tsx}'", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "@recidiviz/eslint-config", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "homepage": "/", + "proxy": "http://localhost:5001", + "devDependencies": { + "@auth0/auth0-spa-js": "^1.20.1", + "@babel/core": "^7.0.0-0", + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "@recidiviz/eslint-config": "^2.0.0", + "@recidiviz/tsconfig": "^1.0.0", + "@types/auth0": "^2.34.13", + "@types/jest": "^27.0.1", + "@types/lodash": "^4.14.182", + "@types/lodash.debounce": "^4.0.7", + "@types/node": "^16.7.13", + "@types/qs": "^6.9.7", + "@types/reach__router": "^1.3.10", + "@types/react": "^17.0.20", + "@types/react-dom": "^17.0.9", + "@types/react-modal": "^3.13.1", + "@types/react-router-dom": "^5.3.3", + "@types/styled-components": "^5.1.24", + "@typescript-eslint/eslint-plugin": "^5.15.0", + "@typescript-eslint/parser": "^5.15.0", + "autoprefixer": "^10.0.2", + "babel-eslint": "^10.1.0", + "babel-plugin-import": "^1.13.3", + "eslint": "^8.11.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^8.5.0", + "eslint-config-react-app": "^7.0.0", + "eslint-import-resolver-typescript": "^2.5.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react": "^7.29.4", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-simple-import-sort": "^7.0.0", + "jest-fetch-mock": "^3.0.3", + "postcss": "^8.1.0", + "prettier": "^2.6.0", + "react-modal": "^3.14.4" + }, + "jest": { + "moduleNameMapper": { + "^d3-(.*)$": "d3-$1/dist/d3-$1" + } + } +} diff --git a/publisher/public/android-chrome-192x192.png b/publisher/public/android-chrome-192x192.png new file mode 100644 index 000000000..0ed733998 Binary files /dev/null and b/publisher/public/android-chrome-192x192.png differ diff --git a/publisher/public/android-chrome-256x256.png b/publisher/public/android-chrome-256x256.png new file mode 100644 index 000000000..35ded2364 Binary files /dev/null and b/publisher/public/android-chrome-256x256.png differ diff --git a/publisher/public/apple-touch-icon.png b/publisher/public/apple-touch-icon.png new file mode 100644 index 000000000..43bdb0658 Binary files /dev/null and b/publisher/public/apple-touch-icon.png differ diff --git a/publisher/public/assets/COURTS_AND_PRETRIAL.xlsx b/publisher/public/assets/COURTS_AND_PRETRIAL.xlsx new file mode 100644 index 000000000..49d998d80 Binary files /dev/null and b/publisher/public/assets/COURTS_AND_PRETRIAL.xlsx differ diff --git a/publisher/public/assets/DEFENSE.xlsx b/publisher/public/assets/DEFENSE.xlsx new file mode 100644 index 000000000..58e6b0e12 Binary files /dev/null and b/publisher/public/assets/DEFENSE.xlsx differ diff --git a/publisher/public/assets/JAILS.xlsx b/publisher/public/assets/JAILS.xlsx new file mode 100644 index 000000000..91b3612f6 Binary files /dev/null and b/publisher/public/assets/JAILS.xlsx differ diff --git a/publisher/public/assets/LAW_ENFORCEMENT.xlsx b/publisher/public/assets/LAW_ENFORCEMENT.xlsx new file mode 100644 index 000000000..94dacbfd5 Binary files /dev/null and b/publisher/public/assets/LAW_ENFORCEMENT.xlsx differ diff --git a/publisher/public/assets/PRISONS.xlsx b/publisher/public/assets/PRISONS.xlsx new file mode 100644 index 000000000..bdb281a68 Binary files /dev/null and b/publisher/public/assets/PRISONS.xlsx differ diff --git a/publisher/public/assets/PROSECUTION.xlsx b/publisher/public/assets/PROSECUTION.xlsx new file mode 100644 index 000000000..523b72d9a Binary files /dev/null and b/publisher/public/assets/PROSECUTION.xlsx differ diff --git a/publisher/public/assets/SUPERVISION.xlsx b/publisher/public/assets/SUPERVISION.xlsx new file mode 100644 index 000000000..e79242b59 Binary files /dev/null and b/publisher/public/assets/SUPERVISION.xlsx differ diff --git a/publisher/public/favicon-16x16.png b/publisher/public/favicon-16x16.png new file mode 100644 index 000000000..1b2ea2905 Binary files /dev/null and b/publisher/public/favicon-16x16.png differ diff --git a/publisher/public/favicon-32x32.png b/publisher/public/favicon-32x32.png new file mode 100644 index 000000000..2a925bdb6 Binary files /dev/null and b/publisher/public/favicon-32x32.png differ diff --git a/publisher/public/favicon.ico b/publisher/public/favicon.ico new file mode 100644 index 000000000..31f3bbba0 Binary files /dev/null and b/publisher/public/favicon.ico differ diff --git a/publisher/public/index.html b/publisher/public/index.html new file mode 100644 index 000000000..4d6164acb --- /dev/null +++ b/publisher/public/index.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + Justice Counts | Control Panel + + + + + + + + + + +
+ + + diff --git a/publisher/public/manifest.json b/publisher/public/manifest.json new file mode 100644 index 000000000..67a52fd68 --- /dev/null +++ b/publisher/public/manifest.json @@ -0,0 +1,20 @@ +{ + "short_name": "Control Panel", + "name": "Control Panel by Recidiviz", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "start_url": ".", + "display": "browser", + "theme_color": "#014c48", + "background_color": "#014c48" +} diff --git a/publisher/public/robots.txt b/publisher/public/robots.txt new file mode 100644 index 000000000..e9e57dc4d --- /dev/null +++ b/publisher/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/publisher/src/App.test.tsx b/publisher/src/App.test.tsx new file mode 100644 index 000000000..c51165b4a --- /dev/null +++ b/publisher/src/App.test.tsx @@ -0,0 +1,55 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import createAuth0Client from "@auth0/auth0-spa-js"; +import { render, screen } from "@testing-library/react"; +import React from "react"; + +import App from "./App"; +import AuthWall from "./components/Auth"; +import { StoreProvider } from "./stores"; + +jest.mock("@auth0/auth0-spa-js"); + +const mockCreateAuth0Client = createAuth0Client as jest.Mock; +const mockIsAuthenticated = jest.fn(); +const mockLoginWithRedirect = jest.fn(); + +beforeEach(() => { + mockCreateAuth0Client.mockResolvedValue({ + isAuthenticated: mockIsAuthenticated, + loginWithRedirect: mockLoginWithRedirect, + }); +}); + +afterEach(() => { + jest.resetAllMocks(); +}); + +test("renders loading on load", () => { + render( + + + + + + ); + const loader = screen.getByTestId("loading"); + expect(loader).toBeInTheDocument(); + + expect.hasAssertions(); +}); diff --git a/publisher/src/App.tsx b/publisher/src/App.tsx new file mode 100644 index 000000000..97b8fcef6 --- /dev/null +++ b/publisher/src/App.tsx @@ -0,0 +1,56 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { observer } from "mobx-react-lite"; +import React, { ReactElement, useEffect } from "react"; +import { Route, Routes, useLocation } from "react-router-dom"; + +import { trackNavigation } from "./analytics"; +import { DataUpload } from "./components/DataUpload"; +import { PageWrapper } from "./components/Forms"; +import Header from "./components/Header"; +import { MetricsView } from "./components/MetricsView"; +import CreateReports from "./components/Reports/CreateReport"; +import ReportDataEntry from "./components/Reports/ReportDataEntry"; +import AccountSettings from "./pages/AccountSettings"; +import Reports from "./pages/Reports"; + +const App: React.FC = (): ReactElement => { + const location = useLocation(); + useEffect(() => { + trackNavigation(location.pathname + location.search); + }, [location]); + + return ( + <> + +
+ + + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); +}; + +export default observer(App); diff --git a/publisher/src/analytics.ts b/publisher/src/analytics.ts new file mode 100644 index 000000000..765a17233 --- /dev/null +++ b/publisher/src/analytics.ts @@ -0,0 +1,208 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { UpdatedMetricsValues, UserAgency } from "./shared/types"; + +const TEST_SENDING_ANALYTICS = false; // used for testing sending analytics in development +const LOG_ANALYTICS = false; // used for logging analytics being sent + +export const identify = ( + userId: string, + metadata?: Record +): void => { + const fullMetadata = metadata || {}; + if (LOG_ANALYTICS) { + // eslint-disable-next-line + console.log( + `[Analytics] Identifying user id: ${userId}, with metadata: ${JSON.stringify( + fullMetadata + )}` + ); + } + if ( + (process.env.NODE_ENV !== "development" && + process.env.NODE_ENV !== "test") || + TEST_SENDING_ANALYTICS + ) { + window.analytics.identify(userId, fullMetadata); + } +}; + +const track = (eventName: string, metadata?: Record): void => { + const fullMetadata = metadata || {}; + if (LOG_ANALYTICS) { + // eslint-disable-next-line + console.log( + `[Analytics] Tracking event name: ${eventName}, with metadata: ${JSON.stringify( + fullMetadata + )}` + ); + } + if ( + (process.env.NODE_ENV !== "development" && + process.env.NODE_ENV !== "test") || + TEST_SENDING_ANALYTICS + ) { + window.analytics.track(eventName, fullMetadata); + } +}; + +export const trackReportCreated = ( + reportId: number, + agency?: UserAgency +): void => { + track("frontend_report_created", { + reportId, + agencyId: agency?.id, + agencyName: agency?.name, + agencyFips: agency?.fips_county_code, + agencyState: agency?.state_code, + agencySystem: agency?.system, + agencySystems: agency?.systems, + }); +}; + +export const trackReportNotStartedToDraft = ( + reportId: number, + agency?: UserAgency +): void => { + track("frontend_report_not_started_to_draft", { + reportId, + agencyId: agency?.id, + agencyName: agency?.name, + agencyFips: agency?.fips_county_code, + agencyState: agency?.state_code, + agencySystem: agency?.system, + agencySystems: agency?.systems, + }); +}; + +export const trackReportPublished = ( + reportId: number, + metrics: UpdatedMetricsValues[], + agency?: UserAgency +): void => { + const metricsReported = metrics.reduce((res: string[], metric) => { + if (metric.value !== null) { + res.push(metric.key); + } + return res; + }, []); + const metricsReportedCount = metricsReported.length; + const metricsReportedWithContext = metrics.reduce((res: string[], metric) => { + if ( + metric.contexts.find( + (context) => context.value !== null && context.value !== undefined + ) + ) { + res.push(metric.key); + } + return res; + }, []); + const metricsReportedWithContextCount = metricsReportedWithContext.length; + const metricsReportedWithDisaggregations = metrics.reduce( + (res: string[], metric) => { + if ( + metric.disaggregations.find((disaggregation) => + disaggregation.dimensions.find( + (dimension) => + dimension.value !== undefined && dimension.value !== null + ) + ) + ) { + res.push(metric.key); + } + return res; + }, + [] + ); + const metricsReportedWithDisaggregationsCount = + metricsReportedWithDisaggregations.length; + const totalMetricsCount = metrics.length; + track("frontend_report_published", { + reportId, + metricsReportedCount, + metricsReportedWithContextCount, + metricsReportedWithDisaggregationsCount, + totalMetricsCount, + agencyId: agency?.id, + agencyName: agency?.name, + agencyFips: agency?.fips_county_code, + agencyState: agency?.state_code, + agencySystem: agency?.system, + agencySystems: agency?.systems, + }); +}; + +export const trackReportUnpublished = ( + reportId: number, + agency?: UserAgency +): void => { + track("frontend_report_unpublished", { + reportId, + agencyId: agency?.id, + agencyName: agency?.name, + agencyFips: agency?.fips_county_code, + agencyState: agency?.state_code, + agencySystem: agency?.system, + agencySystems: agency?.systems, + }); +}; + +export const trackNetworkError = ( + path: string, + method: string, + status: number, + errorMsg: string +): void => { + track("frontend_network_error", { + path, + method, + status, + errorMsg, + }); +}; + +export const trackAutosaveTriggered = (reportId: number): void => { + track("frontend_report_autosave_triggered", { + reportId, + }); +}; + +export const trackAutosaveFailed = (reportId: number): void => { + track("frontend_report_autosave_failed", { + reportId, + }); +}; + +export const trackNavigation = (screen: string): void => { + track("frontend_navigate", { + screen, + }); +}; + +export const trackLoadTime = ( + path: string, + method: string, + loadTime: number +): void => { + track("load_time", { + path, + method, + loadTime, + }); +}; diff --git a/publisher/src/components/Auth/Auth.test.tsx b/publisher/src/components/Auth/Auth.test.tsx new file mode 100644 index 000000000..e1db4761f --- /dev/null +++ b/publisher/src/components/Auth/Auth.test.tsx @@ -0,0 +1,88 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import createAuth0Client from "@auth0/auth0-spa-js"; + +import { AuthStore } from "./AuthStore"; + +jest.mock("@auth0/auth0-spa-js"); + +const mockCreateAuth0Client = createAuth0Client as jest.Mock; +const mockGetUser = jest.fn(); +const mockIsAuthenticated = jest.fn(); +const mockLoginWithRedirect = jest.fn(); + +beforeEach(() => { + mockCreateAuth0Client.mockResolvedValue({ + getUser: mockGetUser, + isAuthenticated: mockIsAuthenticated, + loginWithRedirect: mockLoginWithRedirect, + }); +}); + +afterEach(() => { + jest.resetAllMocks(); +}); + +const testAuthSettings = { + domain: "test.auth0.com", + client_id: "testclientid", + redirect_url: window.location.href, +}; + +const store = new AuthStore({ authSettings: testAuthSettings }); + +test("authorization pending when required", async () => { + expect(store.isAuthorized).toBe(false); + expect(store.isLoading).toBe(true); + + expect.hasAssertions(); +}); + +test("authorized when authenticated", async () => { + mockIsAuthenticated.mockResolvedValue(true); + + await store.authenticate(); + expect(store.isAuthorized).toBe(true); + expect(store.isLoading).toBe(false); + + expect.hasAssertions(); +}); + +test("requires email verification", async () => { + mockGetUser.mockResolvedValue({ emailVerified: false }); + mockIsAuthenticated.mockResolvedValue(true); + + await store.authenticate(); + expect(store.isAuthorized).toBe(true); + expect(store.emailVerified).toBe(false); + + expect.hasAssertions(); +}); + +test("redirect to Auth0 when unauthenticated", async () => { + mockIsAuthenticated.mockResolvedValue(false); + expect(mockLoginWithRedirect.mock.calls.length).toBe(0); + + await store.authenticate(); + expect(mockLoginWithRedirect.mock.calls.length).toBe(1); + expect(mockLoginWithRedirect.mock.calls[0][0]).toEqual({ + appState: { targetUrl: window.location.href }, + }); + + expect.hasAssertions(); +}); diff --git a/publisher/src/components/Auth/AuthStore.ts b/publisher/src/components/Auth/AuthStore.ts new file mode 100644 index 000000000..82cb67ba6 --- /dev/null +++ b/publisher/src/components/Auth/AuthStore.ts @@ -0,0 +1,136 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import createAuth0Client, { + Auth0Client, + Auth0ClientOptions, + GetTokenSilentlyOptions, + User, +} from "@auth0/auth0-spa-js"; +import { makeAutoObservable, runInAction } from "mobx"; +import qs from "qs"; + +import { identify } from "../../analytics"; + +export const APP_METADATA_CLAIM = + "https://dashboard.recidiviz.org/app_metadata"; + +interface AuthStoreProps { + authSettings: Auth0ClientOptions | undefined; +} +export class AuthStore { + readonly authSettings: Auth0ClientOptions | undefined; + + private authClient: Auth0Client | undefined; + + emailVerified?: boolean; + + isAuthorized: boolean; + + isLoading: boolean; + + user?: User; + + constructor({ authSettings }: AuthStoreProps) { + makeAutoObservable(this); + + this.authSettings = authSettings; + this.authClient = undefined; + this.isAuthorized = false; + this.isLoading = true; + } + + private get auth0Client(): Promise { + return createAuth0Client(this.authSettings as Auth0ClientOptions); + } + + async authenticate(): Promise { + const auth0 = await this.auth0Client; + + runInAction(() => { + this.authClient = auth0; + }); + + /* + * Note: need to research this issue further + * Auth0 library responds to the presence of query parameters (code, state, and error) all of which + * indicate that we have been redirected from the Auth0 login page. If they are not removed before + * further interaction with the library, the application enters an infinite loop with newly regenerated + * state/code/error params. The below logic mitigates that by stripping away the parameters + * completely or replaces them with the `targetUrl`. + */ + const urlQuery = qs.parse(window.location.search, { + ignoreQueryPrefix: true, + }); + + if (urlQuery.code && urlQuery.state) { + const { appState } = await auth0.handleRedirectCallback(); + // auth0 params are single-use, must be removed from history or they can cause errors + let replacementUrl; + + if (appState && appState.targetUrl) { + replacementUrl = appState.targetUrl; + } else { + // strip away all query params just to be safe + replacementUrl = `${window.location.origin}${window.location.pathname}`; + } + + window.history.replaceState({}, document.title, replacementUrl); + } + + if (await auth0.isAuthenticated()) { + const user = await auth0.getUser(); + if (user?.sub) { + identify(user.sub, { + name: user.name, + email: user.email, + }); + } + + runInAction(() => { + this.isLoading = false; + this.user = user; + this.isAuthorized = true; + this.emailVerified = Boolean(user?.email_verified); + }); + } else { + await auth0.loginWithRedirect({ + appState: { targetUrl: window.location.href }, + }); + } + } + + async logoutUser() { + runInAction(() => { + this.isAuthorized = false; + this.isLoading = true; + }); + + return this.authClient?.logout({ returnTo: window.location.origin }); + } + + async getToken(options?: GetTokenSilentlyOptions): Promise { + try { + if (this.authClient) { + return this.authClient.getTokenSilently(options); + } + return Promise.reject(new Error("No auth client initialized.")); + } catch (error) { + throw new Error(`Unable to retrieve token - ${error}`); + } + } +} diff --git a/publisher/src/components/Auth/AuthWall.tsx b/publisher/src/components/Auth/AuthWall.tsx new file mode 100644 index 000000000..3440ab135 --- /dev/null +++ b/publisher/src/components/Auth/AuthWall.tsx @@ -0,0 +1,57 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { when } from "mobx"; +import { observer } from "mobx-react-lite"; +import React, { useEffect } from "react"; + +import { useStore } from "../../stores/StoreProvider"; +import { Loading } from "../Loading"; +import UnauthorizedPage from "./UnauthorizedPage"; +import VerificationPage from "./VerificationPage"; + +const AuthWall: React.FC = ({ + children, +}): React.ReactElement | null => { + const { authStore } = useStore(); + + useEffect( + () => + // return when's disposer so it is cleaned up if it never runs + when( + () => !authStore.isAuthorized, + () => authStore.authenticate() + ), + [authStore] + ); + + if (authStore.isLoading) { + return ; + } + + if (!authStore.isAuthorized) { + return ; + } + + if (!authStore.emailVerified) { + return ; + } + + return authStore.isAuthorized ? <>{children} : null; +}; + +export default observer(AuthWall); diff --git a/publisher/src/components/Auth/UnauthorizedPage.tsx b/publisher/src/components/Auth/UnauthorizedPage.tsx new file mode 100644 index 000000000..ae52f9e05 --- /dev/null +++ b/publisher/src/components/Auth/UnauthorizedPage.tsx @@ -0,0 +1,48 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { + Button, + ErrorPage, + Link as TypographyLink, +} from "@recidiviz/design-system"; +import React from "react"; + +import { useStore } from "../../stores/StoreProvider"; + +const UnauthorizedPage = () => { + const { authStore } = useStore(); + + return ( + +

+ This page is currently unavailable for your account. Please reach out to{" "} + + Recidiviz Support + {" "} + with any questions. +

+ + {/* Optional: takes user back to login screen */} + +
+ ); +}; + +export default UnauthorizedPage; diff --git a/publisher/src/components/Auth/VerificationPage.tsx b/publisher/src/components/Auth/VerificationPage.tsx new file mode 100644 index 000000000..335b30136 --- /dev/null +++ b/publisher/src/components/Auth/VerificationPage.tsx @@ -0,0 +1,126 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import styled from "styled-components/macro"; + +import { useStore } from "../../stores/StoreProvider"; +import logo from "../assets/jc-logo-green-vector.png"; +import { palette, typography } from "../GlobalStyles"; + +export const PageContainer = styled.div` + display: flex; + justify-content: center; + height: 100%; + align-items: center; +`; + +export const ContentContainer = styled.div` + display: flex; + flex-direction: column; + width: 652px; +`; +export const LogoContainer = styled.div` + display: flex; +`; +export const Logo = styled.img` + width: 60px; + height: 60px; +`; +export const LogoTitleContainer = styled.div` + ${typography.sizeCSS.medium} + display: flex; + flex-direction: column; + padding-left: 10px; + padding-top: 10px; +`; + +export const VerificationPageHeader = styled.div` + ${typography.sizeCSS.large} + padding-top: 50px; +`; + +export const ParagraphContainer = styled.div` + ${typography.sizeCSS.medium} + padding-top: 24px; + padding-bottom: 32px; + color: ${palette.solid.grey}; +`; + +export const JusticeCountsSupportLink = styled.a` + color: ${palette.solid.green}; +`; + +export const VerificationLogoutButton = styled.button` + width: 147px; + height: 56px; + display: flex; + justify-content: center; + align-items: center; + background: ${palette.solid.blue}; + border: 1px solid ${palette.highlight.grey3}; + color: ${palette.solid.white}; + font-size: 18px; + &:hover { + cursor: pointer; + } +`; + +const VerificationPage = () => { + const { authStore } = useStore(); + const handleLogout = () => authStore.logoutUser(); + + return ( + + + + + +
Publisher
+
By Justice Counts
+
+
+ + Please verify your email. + + +

+ If you have just signed up for an account, please check your inbox + for an email asking you to verify your email address. After you + click the verification button or link in that email, you can reach + the home page below. +

+
+

+ If you have reached this page by mistake, please try to log in + again. If you are still having trouble, please reach out to{" "} + + Justice Counts Support + + . +

+
+ + + Logout + +
+
+ ); +}; + +export default VerificationPage; diff --git a/publisher/src/components/Auth/index.ts b/publisher/src/components/Auth/index.ts new file mode 100644 index 000000000..0c1fcc874 --- /dev/null +++ b/publisher/src/components/Auth/index.ts @@ -0,0 +1,19 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./AuthStore"; +export { default } from "./AuthWall"; diff --git a/publisher/src/components/Badge/Badge.tsx b/publisher/src/components/Badge/Badge.tsx new file mode 100644 index 000000000..0ecc41b1c --- /dev/null +++ b/publisher/src/components/Badge/Badge.tsx @@ -0,0 +1,77 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= +import React from "react"; +import styled from "styled-components/macro"; + +import { palette } from "../GlobalStyles"; +import { MiniLoader } from "../Loading/MiniLoader"; + +export type BadgeColors = "RED" | "GREEN" | "ORANGE" | "GREY"; + +export type BadgeColorMapping = { [key: string]: BadgeColors }; + +export type BadgeProps = { + color: BadgeColors; + disabled?: boolean; + loading?: boolean; +}; + +export const BadgeElement = styled.div<{ + color?: BadgeColors; + disabled?: boolean; +}>` + height: 24px; + display: flex; + justify-content: center; + align-items: center; + background: ${({ color, disabled }) => { + if (color === "GREY" || disabled) { + return palette.highlight.grey9; + } + if (color === "RED") { + return palette.solid.red; + } + if (color === "GREEN") { + return palette.solid.green; + } + if (color === "ORANGE") { + return palette.solid.orange; + } + return palette.highlight.grey5; + }}; + color: ${palette.solid.white}; + padding: 4px 8px; + margin-left: 10px; + font-size: 0.65rem; + font-weight: 600; + white-space: nowrap; + text-transform: capitalize; +`; + +export const Badge: React.FC = ({ + color, + disabled, + loading, + children, +}) => { + return ( + + {children} + {loading && } + + ); +}; diff --git a/publisher/src/components/Badge/index.ts b/publisher/src/components/Badge/index.ts new file mode 100644 index 000000000..891be9860 --- /dev/null +++ b/publisher/src/components/Badge/index.ts @@ -0,0 +1,18 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./Badge"; diff --git a/publisher/src/components/DataUpload/DataUpload.styles.tsx b/publisher/src/components/DataUpload/DataUpload.styles.tsx new file mode 100644 index 000000000..dc531368d --- /dev/null +++ b/publisher/src/components/DataUpload/DataUpload.styles.tsx @@ -0,0 +1,483 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import styled from "styled-components/macro"; + +import { rem } from "../../utils"; +import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; +import { + Cell, + LabelCell, + LabelRow, + PageTitle, + Row, + TabbedBar, + Table, +} from "../Reports"; + +const ROW_HEIGHT = 42; + +export const DataUploadContainer = styled.div` + width: 100%; + height: 100%; + position: absolute; + top: 0; + z-index: 5; + background: ${palette.solid.white}; + + a, + a:visited { + color: ${palette.solid.blue}; + text-decoration: none; + transition: 0.2s ease; + } + + a:hover { + color: ${palette.solid.darkblue}; + cursor: pointer; + } +`; + +export const DataUploadHeader = styled.div<{ transparent?: boolean }>` + width: 100%; + height: ${HEADER_BAR_HEIGHT}px; + display: flex; + justify-content: space-between; + align-items: center; + position: fixed; + top: 0; + padding-right: 24px; + ${({ transparent }) => + !transparent && + ` + background: ${palette.solid.white}; + border-bottom: 1px solid ${palette.highlight.grey3}; + `} +`; + +export const MediumPageTitle = styled(PageTitle)` + font-size: ${rem("50px")}; +`; + +export const Instructions = styled.div` + height: 100%; + flex: 1 1 50%; + padding: 103px; + display: flex; + flex-direction: column; + overflow-y: scroll; + ${typography.sizeCSS.medium} + + h1 { + ${typography.sizeCSS.title} + } + + h2 { + ${typography.sizeCSS.large} + margin: 15px 0; + } + + h3 { + ${typography.sizeCSS.large} + font-size: ${rem("22px")}; + color: ${palette.highlight.grey10}; + margin-top: 15px; + } + + h1, + h2, + h3 { + text-transform: capitalize; + } + + ol, + ul, + p { + margin: 10px 0; + } + + ol, + ul { + line-height: 1.8rem; + } + + li { + margin-left: 50px; + } + + li ul { + margin: 0; + } + + table { + max-width: 50%; + width: max-content; + margin: 15px 0; + text-align: left; + border: 0.5px solid black; + border-spacing: 0; + ${typography.sizeCSS.normal}; + } + + thead { + background: ${palette.highlight.grey2}; + } + + th, + td { + border: 0.5px solid black; + padding: 5px 20px; + } +`; + +export const ButtonWrapper = styled.div` + display: flex; + flex-wrap: wrap; + gap: 10px; + margin: 13px 0; +`; + +export type ButtonTypes = + | "light-border" + | "border" + | "borderless" + | "blue" + | "red"; + +export const Button = styled.div<{ type?: ButtonTypes }>` + ${typography.sizeCSS.normal}; + display: flex; + align-items: center; + padding: 10px 15px; + border-radius: 3px; + gap: 16px; + text-transform: capitalize; + + ${({ type }) => { + if (type === "light-border") { + return ` + background: none; + border: 1px solid ${palette.solid.white}; + color: ${palette.solid.white}; + border-radius: 4px; + `; + } + if (type === "border") { + return ` + border: 1px solid ${palette.highlight.grey4}; + border-radius: 4px; + `; + } + if (type === "borderless") { + return ` + background: none; + color: ${palette.highlight.grey10}; + `; + } + if (type === "blue") { + return ` + background: ${palette.solid.blue}; + color: ${palette.solid.white}; + `; + } + if (type === "red") { + return ` + background: ${palette.solid.red}; + color: ${palette.solid.white}; + `; + } + return ` + background: ${palette.highlight.grey1}; + color: ${palette.highlight.grey10}; + `; + }} + + &:hover { + cursor: pointer; + ${({ type }) => { + if (type === "border") { + return `background: ${palette.highlight.grey1};`; + } + if (type === "borderless") { + return `opacity: 0.8;`; + } + if (type === "blue" || type === "red") { + return `opacity: 0.9;`; + } + return `background: ${palette.highlight.grey2};`; + }}; + } + + a { + ${typography.sizeCSS.small}; + width: fit-content; + text-decoration: none; + color: ${palette.solid.blue}; + display: flex; + align-items: center; + } +`; + +export const DownloadTemplateBox = styled.div` + ${typography.sizeCSS.normal}; + display: flex; + align-items: center; + padding: 10px 15px; + border-radius: 3px; + gap: 16px; + text-transform: capitalize; + background: none; + border: 1px solid ${palette.highlight.grey4}; + border-radius: 4px; + + a { + ${typography.sizeCSS.small}; + display: block; + width: fit-content; + text-decoration: none; + color: ${palette.solid.blue}; + } +`; + +export const UploadButtonLabel = styled.label` + display: inline-block; + border-bottom: 1px solid ${palette.solid.white}; + + &:hover { + cursor: pointer; + } +`; + +export const UploadButtonInput = styled.input` + display: none; +`; + +export const DropdownItemUploadInput = styled.input` + display: none; +`; + +export const Icon = styled.img<{ grayscale?: boolean }>` + width: 16px; + aspect-ratio: auto; + margin-left: 10px; + ${({ grayscale }) => grayscale && `filter: grayscale(1);`} +`; + +export const ModalLoadingWrapper = styled.div` + div { + height: 100%; + top: 25%; + } +`; + +export const ModalErrorWrapper = styled.div` + display: flex; + justify-content: center; + margin-top: 25px; +`; + +export const DownloadIcon = styled.img` + width: 20px; + margin-right: 5px; +`; + +export const ActionsContainer = styled.div` + ${typography.sizeCSS.normal}; + height: 100%; + display: flex; + justify-content: flex-end; + align-items: center; + background: ${palette.solid.offwhite}; + gap: 10px; + padding-left: 20px; + position: absolute; + right: 22px; + z-index: 3; + width: 25vw; +`; + +export const ActionButton = styled.div<{ red?: boolean }>` + color: ${({ red }) => (red ? palette.solid.red : palette.solid.blue)}; + &:hover { + color: ${palette.solid.darkgrey}; + } +`; + +export const UploadFileContainer = styled.div` + height: 100%; + display: flex; +`; + +export const DragDropContainer = styled.div<{ dragging?: boolean }>` + height: 100%; + display: flex; + flex-direction: column; + flex: 1 1 50%; + align-items: center; + justify-content: center; + background: ${({ dragging }) => + dragging ? palette.solid.darkblue : palette.solid.blue}; + color: ${palette.solid.white}; +`; + +export const UserPromptContainer = styled.div` + width: 100%; + min-height: 100%; + display: flex; + justify-content: center; + align-items: center; + padding-top: ${HEADER_BAR_HEIGHT + 80}px; + padding-bottom: 80px; +`; + +export const UserPromptWrapper = styled.div` + width: 100%; + max-width: 50%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; +`; + +export const UserPromptTitle = styled.div` + ${typography.sizeCSS.title}; +`; + +export const UserPromptDescription = styled.div` + ${typography.sizeCSS.medium}; + margin: 8px 0; + + span { + text-transform: capitalize; + } +`; + +export const UserPromptErrorContainer = styled.div` + width: 100%; + margin-top: 19px; +`; + +export const UserPromptError = styled.div` + margin-bottom: 40px; +`; + +export const MetricTitle = styled.div` + ${typography.sizeCSS.large}; + border-top: 1px solid ${palette.highlight.grey4}; + padding: 16px 0; +`; + +export const ErrorIconWrapper = styled.div` + display: flex; + align-items: center; + gap: 5px; +`; +export const ErrorMessageWrapper = styled.div` + ${typography.sizeCSS.medium}; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; +`; + +export const ErrorMessageTitle = styled.div` + display: block; +`; + +export const ErrorMessageDescription = styled.div` + display: block; +`; + +export const ErrorAdditionalInfo = styled.div` + ${typography.sizeCSS.normal}; + margin: 8px 0 13px 0; +`; + +export const SelectSystemOptions = styled.div` + width: 100%; + margin-top: 32px; +`; + +export const SystemName = styled.div` + ${typography.sizeCSS.large}; + padding: 20px 0; + border-top: 1px solid ${palette.highlight.grey4}; + display: flex; + justify-content: space-between; + align-items: center; + text-transform: uppercase; + + &:hover { + cursor: pointer; + color: ${palette.solid.blue}; + } +`; + +export const FileName = styled.div<{ error?: boolean }>` + ${typography.sizeCSS.medium}; + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; + color: ${({ error }) => (error ? palette.solid.red : palette.solid.green)}; +`; + +export const ConfirmationPageContainer = styled.div` + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +`; + +export const UploadedFilesContainer = styled.div` + height: 100%; + padding: ${ROW_HEIGHT}px 0; + overflow-y: scroll; +`; + +export const UploadedFilesTable = styled(Table)` + padding: unset; +`; + +export const ExtendedTabbedBar = styled(TabbedBar)` + height: 66px; +`; + +export const ExtendedRow = styled(Row)` + color: ${({ selected }) => selected && palette.highlight.grey9}; + position: relative; + transition: unset; +`; + +export const ExtendedLabelRow = styled(LabelRow)` + position: fixed; + background: ${palette.solid.white}; + z-index: 1; +`; + +export const ExtendedCell = styled(Cell)` + &:first-child { + flex: 4 1 auto; + } +`; + +export const ExtendedLabelCell = styled(LabelCell)` + &:first-child { + flex: 4 1 auto; + } +`; diff --git a/publisher/src/components/DataUpload/DataUpload.tsx b/publisher/src/components/DataUpload/DataUpload.tsx new file mode 100644 index 000000000..569f8d1d3 --- /dev/null +++ b/publisher/src/components/DataUpload/DataUpload.tsx @@ -0,0 +1,323 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { observer } from "mobx-react-lite"; +import React, { Fragment, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import { AgencySystems } from "../../shared/types"; +import { useStore } from "../../stores"; +import { removeSnakeCase } from "../../utils"; +import { ReactComponent as ErrorIcon } from "../assets/error-icon.svg"; +import logoImg from "../assets/jc-logo-vector.png"; +import { ReactComponent as WarningIcon } from "../assets/warning-icon.svg"; +import { Logo, LogoContainer } from "../Header"; +import { Loading } from "../Loading"; +import { showToast } from "../Toast"; +import { + Button, + ButtonWrapper, + DataUploadContainer, + DataUploadHeader, + ErrorAdditionalInfo, + ErrorIconWrapper, + ErrorMessageDescription, + ErrorMessageTitle, + ErrorMessageWrapper, + FileName, + MetricTitle, + SystemSelection, + UploadFile, + UserPromptContainer, + UserPromptDescription, + UserPromptError, + UserPromptErrorContainer, + UserPromptTitle, + UserPromptWrapper, +} from "."; + +export type UploadedFileStatus = "UPLOADED" | "INGESTED" | "ERRORED"; + +export type UploadedFileAttempt = { + name: string; + upload_attempt_timestamp: number; + status?: "ERRORED"; +}; + +export type UploadedFile = { + name: string; + id: number; + uploaded_at: number; + ingested_at: number; + uploaded_by: string; + system: AgencySystems; + status: UploadedFileStatus | null; +}; + +/** + * The systems in EXCLUDED_SYSTEMS are sub-systems of the SUPERVISION system, + * and should not render a separate template & instructions. + * + * Example: if an agency has the following systems ["SUPERVISION", "PAROLE", "PROBATION"], + * the UI should render a template & instructions for the SUPERVISION system. + */ +export const EXCLUDED_SYSTEMS = ["PAROLE", "PROBATION", "POST_RELEASE"]; + +export const systemToTemplateSpreadsheetFileName: { [system: string]: string } = + { + LAW_ENFORCEMENT: "LAW_ENFORCEMENT.xlsx", + PROSECUTION: "PROSECUTION.xlsx", + DEFENSE: "DEFENSE.xlsx", + COURTS_AND_PRETRIAL: "COURTS_AND_PRETRIAL.xlsx", + JAILS: "JAILS.xlsx", + PRISONS: "PRISONS.xlsx", + SUPERVISION: "SUPERVISION.xlsx", + PAROLE: "SUPERVISION.xlsx", + PROBATION: "SUPERVISION.xlsx", + }; + +export const DataUpload: React.FC = observer(() => { + const { userStore, reportStore } = useStore(); + const navigate = useNavigate(); + const userSystems = + userStore.currentAgency?.systems.filter( + (system) => !EXCLUDED_SYSTEMS.includes(system) + ) || []; + + const [isLoading, setIsLoading] = useState(false); + const [uploadError, setUploadError] = useState(false); + const [selectedFile, setSelectedFile] = useState(); + const [selectedSystem, setSelectedSystem] = useState< + AgencySystems | undefined + >(userSystems.length === 1 ? userSystems[0] : undefined); + + const handleFileUpload = async ( + file: File, + system: AgencySystems + ): Promise => { + if (file && system && userStore.currentAgencyId) { + const formData = new FormData(); + formData.append("file", file); + formData.append("name", file.name); + formData.append("system", system); + formData.append("ingest_on_upload", "True"); + formData.append("agency_id", userStore.currentAgencyId.toString()); + + const response = await reportStore.uploadExcelSpreadsheet(formData); + setIsLoading(false); + + if (response instanceof Error) { + setUploadError(true); + return showToast("Failed to upload. Please try again.", false, "red"); + } + + setUploadError(false); + showToast( + "File uploaded successfully and is pending processing by a Justice Counts administrator.", + true, + undefined, + 3500 + ); + /** Placeholder - this should navigate to the confirmation component */ + navigate("/"); + } + }; + + const handleSystemSelection = (file: File, system: AgencySystems) => { + setIsLoading(true); + setSelectedSystem(system); + handleFileUpload(file, system); + setSelectedFile(undefined); + }; + + if (isLoading) { + return ( + + + + ); + } + + const renderCurrentUploadStep = (): JSX.Element => { + if (selectedFile) { + /** System Selection Step (for multi-system users) */ + return ( + + ); + } + + /** Upload Error/Warnings Step */ + if (uploadError) { + /** This object is temporary for the purpose of displaying each UI state */ + const mockErrors = [ + { + metricTitle: "Releases", + errorsAndWarnings: [ + { + type: "error", + errorTitle: "Breakdown not recognized", + errorDescription: "Label Not Recognized", + }, + { + type: "error", + errorTitle: "Breakdown not recognized", + errorDescription: "Label Not Recognized", + }, + { + type: "error", + errorTitle: "Breakdown not recognized", + errorDescription: "Label Not Recognized", + }, + { + type: "warning", + errorTitle: "Breakdown not recognized", + errorDescription: "Label Not Recognized", + }, + ], + }, + { + metricTitle: "Admissions", + errorsAndWarnings: [ + { + type: "error", + errorTitle: "Missing value", + errorDescription: "August 2022: Total", + additionalInfo: + "The total value for Admissions will be shown as the sum of the breakdowns.", + }, + { + type: "error", + errorTitle: "Breakdown not recognized", + errorDescription: "Label Not Recognized", + }, + { + type: "error", + errorTitle: "Breakdown not recognized", + errorDescription: "Label Not Recognized", + }, + { + type: "warning", + errorTitle: "Breakdown not recognized", + errorDescription: "Label Not Recognized", + }, + ], + }, + ]; + + const systemFileName = + selectedSystem && systemToTemplateSpreadsheetFileName[selectedSystem]; + + return ( + + + + + File Name.xls + + Uh oh, we found 4 errors. + + We ran into a few discrepancies between the uploaded data and the + Justice Counts format for the{" "} + + + {selectedSystem && + removeSnakeCase(selectedSystem).toLowerCase()} + + {" "} + system. To continue, please resolve the errors in your file and + reupload. + + + + + + + + {mockErrors.map((item) => ( + + {item.metricTitle} + + {item.errorsAndWarnings?.map((errorItem) => ( + + + {errorItem.type === "error" ? ( + + ) : ( + + )} + + + + {errorItem.errorTitle} + + + {errorItem.errorDescription} + + + + + {errorItem.additionalInfo} + + + ))} + + ))} + + + + ); + } + + /** Upload File Step */ + return ( + + ); + }; + + return ( + + + navigate("/")}> + + + + + + + {renderCurrentUploadStep()} + + ); +}); diff --git a/publisher/src/components/DataUpload/InstructionsTemplate.tsx b/publisher/src/components/DataUpload/InstructionsTemplate.tsx new file mode 100644 index 000000000..adcd127e6 --- /dev/null +++ b/publisher/src/components/DataUpload/InstructionsTemplate.tsx @@ -0,0 +1,558 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; + +import { removeSnakeCase } from "../../utils"; +import { ReactComponent as SpreadsheetIcon } from "../assets/microsoft-excel-icon.svg"; +import { + ButtonWrapper, + DownloadTemplateBox, + systemToTemplateSpreadsheetFileName, +} from "."; + +export type GeneralInstructionsTemplateParams = { + systems: string[]; +}; + +export type SystemsInstructionsTemplateParams = { + system: string; +}; + +type SystemDetails = { + name: string; + url: string; + metric: string; + metric_sheet_name: string; + metric_disaggregation_sheet_name: string; + metric_category: string; + metric_disaggregation: string; + metric_disaggregation_column_name: string; + metric_disaggregation_values: { [name: string]: number | string }[]; +}; + +export const systemToDetails: { [system: string]: SystemDetails } = { + LAW_ENFORCEMENT: { + name: "Law Enforcement", + url: "https://justicecounts.csgjusticecenter.org/metrics/sectors/law-enforcement/", + metric: "Reported Crime", + metric_sheet_name: "reported_crime", + metric_disaggregation_sheet_name: "reported_crime_by_type", + metric_category: "Population Movements", + metric_disaggregation: "offense type", + metric_disaggregation_column_name: "offense_type", + metric_disaggregation_values: [ + { name: "Person", value: 3291 }, + { name: "Property", value: 1293 }, + { name: "Drug", value: 504 }, + { name: "Other", value: 34 }, + { name: "Unknown", value: 3 }, + ], + }, + DEFENSE: { + name: "Defense", + url: "https://justicecounts.csgjusticecenter.org/metrics/sectors/defense/", + metric: "Cases Appointed Counsel", + metric_sheet_name: "cases_appointed", + metric_disaggregation_sheet_name: "cases_appointed_by_severity", + metric_category: "Population Movements", + metric_disaggregation: "case severity", + metric_disaggregation_column_name: "case_severity", + metric_disaggregation_values: [ + { name: "Felony", value: 3291 }, + { name: "Misdemeanor", value: 1293 }, + { name: "Infraction", value: 504 }, + { name: "Unknown", value: 3 }, + ], + }, + PROSECUTION: { + name: "Prosecution", + url: "https://justicecounts.csgjusticecenter.org/metrics/sectors/prosecution/", + metric: "Cases Declined", + metric_sheet_name: "cases_declined", + metric_disaggregation_sheet_name: "cases_declined_by_severity", + metric_category: "Operations and Dynamics", + metric_disaggregation: "case severity", + metric_disaggregation_column_name: "case_severity", + metric_disaggregation_values: [ + { name: "Felony", value: 3291 }, + { name: "Misdemeanor", value: 1293 }, + { name: "Infraction", value: 504 }, + { name: "Unknown", value: 3 }, + ], + }, + COURTS_AND_PRETRIAL: { + name: "Courts and Pretrial", + url: "https://justicecounts.csgjusticecenter.org/metrics/sectors/courts-pretrial/", + metric: "Pretrial Releases", + metric_sheet_name: "pretrial_releases", + metric_disaggregation_sheet_name: "pretrial_releases_by_type", + metric_category: "Operations and Dynamics", + metric_disaggregation: "release type", + metric_disaggregation_column_name: "release_type", + metric_disaggregation_values: [ + { name: "ROR", value: 3291 }, + { name: "Monetary bail", value: 1293 }, + { name: "Supervision", value: 504 }, + { name: "Other", value: 34 }, + { name: "Unknown", value: 3 }, + ], + }, + JAILS: { + name: "Jails", + url: "https://justicecounts.csgjusticecenter.org/metrics/sectors/jails/", + metric: "Admissions", + metric_sheet_name: "admissions", + metric_disaggregation_sheet_name: "admissions_by_type", + metric_category: "Population Movements", + metric_disaggregation: "admission type", + metric_disaggregation_column_name: "admission_type", + metric_disaggregation_values: [ + { name: "Pretrial", value: 3291 }, + { name: "Sentenced", value: 1293 }, + { name: "Transfer or Hold", value: 504 }, + { name: "Unknown", value: 3 }, + ], + }, + PRISONS: { + name: "Prisons", + url: "https://justicecounts.csgjusticecenter.org/metrics/sectors/prisons/", + metric: "Admissions", + metric_sheet_name: "admissions", + metric_disaggregation_sheet_name: "admissions_by_type", + metric_category: "Population Movements", + metric_disaggregation: "admission type", + metric_disaggregation_column_name: "admission_type", + metric_disaggregation_values: [ + { name: "New Sentence", value: 3291 }, + { name: "Transfer or Hold", value: 1293 }, + { name: "Supervision Violation or Revocation", value: 504 }, + { name: "Other", value: 34 }, + { name: "Unknown", value: 3 }, + ], + }, + SUPERVISION: { + name: "Supervision", + url: "https://justicecounts.csgjusticecenter.org/metrics/sectors/community-supervision/", + metric: "New Supervision Cases", + metric_sheet_name: "new_cases", + metric_disaggregation_sheet_name: "new_cases_by_type", + metric_category: "Population Movements", + metric_disaggregation: "supervision type", + metric_disaggregation_column_name: "supervision_type", + metric_disaggregation_values: [ + { name: "Active", value: 3291 }, + { name: "Passive", value: 1293 }, + { name: "Unknown", value: 504 }, + ], + }, +}; + +export const GeneralInstructions: React.FC< + GeneralInstructionsTemplateParams +> = ({ systems }) => { + return ( + <> +

How to Upload Data to Justice Counts

+

+ Agencies participating in Justice Counts have two options for reporting + their assigned metrics: +

+
    +
  1. + Filling out the autogenerated reports on the{" "} + + Reports + {" "} + page +
  2. +
  3. Uploading an Excel spreadsheet
  4. +
+

+ If you choose the second option, we require that you upload the + spreadsheet in a particular format, so we can build automation on + our end to easily upload this data into our platform. +

+ +

Templates

+ + + {systems.map((system) => { + const systemName = removeSnakeCase(system).toLowerCase(); + const systemFileName = systemToTemplateSpreadsheetFileName[system]; + + return ( + + + + + {systemName} + + Download + + + + ); + })} + + +

+ Complete, downloadable spreadsheet templates can be found above. We + suggest that you download the template and review it, and then read the + description below for more detail. +

+

High-Level Summary

+

+ In this section, we provide instructions applicable to all agencies. In + the next section, we provide instructions specific to your agency. +

+

Basics

+

+ You will need to generate one Excel workbook for each criminal justice + system that your agency is reporting data for. Your agency is + responsible for reporting data for:{" "} + {/* replace last comma with "and": https://stackoverflow.com/a/41035407 */} + {systems + .map((system) => systemToDetails[system].name) + .join(", ") + .replace(/,(?!.*,)/gim, " and")} + . +

+

+ Include one sheet (or tab) in the workbook for each Justice Counts + metric defined for the system. See the{" "} + + Justice Counts website + {" "} + for a list and description of these metrics. +

+

+ We require that each sheet is given a standard name.{" "} + Refer to your template file for the valid sheet names. +

+

+ All sheets will have columns for year and value. Monthly + metrics will also have a column for month. +

+

+ To report data for a metric for a particular time period, add a new row + to the sheet. Fill in the appropriate values for the year and{" "} + month columns, and report the metric value in the value{" "} + column. +

+

+ The year should be in the format 20XX. The month should either be a + number [1 … 12] or the full month name [January … February]. The metric + value should be numeric and contain no other characters (commas are + allowed, e.g. 1,000). +

+ +

The sheets will look roughly like this:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
yearmonthvalue
202175088
202185270
202195113
2021105196
2021115237
2021125123
+

Disaggregations

+

+ If the metric asks you to break the value down into different categories + (e.g. separating out crimes by offense type), add an additional sheet to + the workbook that includes a column with the name of the category (e.g.{" "} + offense_type). Each row should specify a different value for that + category (e.g. person, property, drug). +

+

+ We require that each sheet and column is given a standard name.{" "} + + Refer to your template file for the name of the new sheet, the name of + the new column for category names, and the valid values for this + column. + +

+

+ + Please only provide valid category names (as seen in your template + file) in the new column.{" "} + + If your agency categorizes the metric differently, group any unmatched + data into the Other category. +

+ +

+ For instance, a sheet for reported_crimes_by_type might look like + this: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
yearmonthvalueoffense_type
20217Person3291
20217Property1293
20217Drug504
20217Other372
20217Unknown53
+

+ Fill in as many categories as you can. Skip any that are not applicable + to your agency. +

+

Multiple Jurisdictions

+

+ If you are reporting data for multiple jurisdictions, you should also + add a column to each sheet titled jurisdiction_name. The value of + this column should be the name of the jurisdiction to which that data + point belongs. For instance: +

+ + + + + + + + + + + + + + + + + + + + + +
yearjurisdiction_namevalue
2021County X1000
2021County Y2000
+ + ); +}; + +export const SystemsInstructions: React.FC< + SystemsInstructionsTemplateParams +> = ({ system }) => { + const systemDetails = systemToDetails[system]; + return ( + <> +

+ {systemDetails.name} agencies are required to report the set of metrics + described on the{" "} + + Justice Counts website + + . +

+

+ For this example, consider the {systemDetails.metric} metric in + the {systemDetails.metric_category} section. +

+

+ To report data for this metric, add two sheets to your Excel workbook: + one called {systemDetails.metric_sheet_name} and one called{" "} + {systemDetails.metric_disaggregation_sheet_name}. +

+

+ In the {systemDetails.metric_sheet_name} sheet, we'll report + total values for each month: +

+ + + + + + {system === "SUPERVISION" ? : null} + + + + + + + + {system === "SUPERVISION" ? : null} + + + + + + {system === "SUPERVISION" ? : null} + + + {system === "SUPERVISION" ? ( + <> + + + + + + + + + + + + + + ) : null} + +
yearmonthsystemvalue
20217Parole5088
20218Parole5270
20217Probation2093
20218Probation2392
+ {system === "SUPERVISION" ? ( +

+ Note: If you are able to report separately for Parole and + Probation, then you should fill out rows for both systems. If you have + only aggregate data, leave the system column blank, or write + "Both". +

+ ) : null} +

+ In the {systemDetails.metric_disaggregation_sheet_name} sheet, + break down values by {systemDetails.metric_disaggregation}: +

+ + + + + + {system === "SUPERVISION" ? : null} + + + + + + {systemDetails.metric_disaggregation_values.map((obj) => { + return ( + + + + {system === "SUPERVISION" ? : null} + + + + ); + })} + {system === "SUPERVISION" + ? systemDetails.metric_disaggregation_values.map((obj) => { + return ( + + + + + + + + ); + }) + : null} + +
yearmonthsystem{systemDetails.metric_disaggregation_column_name}value
20217Parole{obj.name}{obj.value}
20217Probation{obj.name}...
+

+ Provide as many categories as you can, but feel free to skip the ones + that you don't have. +

+

+ To add data for another month, just add more rows. Note that the order + of the rows does not matter, though grouping rows from the same + month/year and arranging them in ascending or descending order is + preferable. +

+ + ); +}; diff --git a/publisher/src/components/DataUpload/SystemSelection.tsx b/publisher/src/components/DataUpload/SystemSelection.tsx new file mode 100644 index 000000000..550ad15e3 --- /dev/null +++ b/publisher/src/components/DataUpload/SystemSelection.tsx @@ -0,0 +1,66 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; + +import { AgencySystems } from "../../shared/types"; +import { removeSnakeCase } from "../../utils"; +import { ReactComponent as CheckIcon } from "../assets/check-icon.svg"; +import { + FileName, + SelectSystemOptions, + SystemName, + UserPromptContainer, + UserPromptTitle, + UserPromptWrapper, +} from "."; + +type SystemSelectionProps = { + selectedFile: File; + userSystems: AgencySystems[]; + handleSystemSelection: (file: File, system: AgencySystems) => void; +}; + +export const SystemSelection: React.FC = ({ + selectedFile, + userSystems, + handleSystemSelection, +}) => { + return ( + + + + + {selectedFile.name} + + Which system is this data for? + + + {userSystems.map((system) => ( + handleSystemSelection(selectedFile, system)} + > + {removeSnakeCase(system)} + + + ))} + + + + ); +}; diff --git a/publisher/src/components/DataUpload/UploadFile.tsx b/publisher/src/components/DataUpload/UploadFile.tsx new file mode 100644 index 000000000..ba2c67a54 --- /dev/null +++ b/publisher/src/components/DataUpload/UploadFile.tsx @@ -0,0 +1,165 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { Fragment, useEffect, useRef, useState } from "react"; + +import { AgencySystems } from "../../shared/types"; +import { removeSnakeCase } from "../../utils"; +import { ReactComponent as FileIcon } from "../assets/file-icon.svg"; +import { showToast } from "../Toast"; +import { + DragDropContainer, + GeneralInstructions, + Instructions, + SystemsInstructions, + UploadButtonInput, + UploadButtonLabel, + UploadFileContainer, +} from "."; + +type UploadFileProps = { + userSystems: AgencySystems[]; + setIsLoading: React.Dispatch>; + setSelectedFile: React.Dispatch>; + handleFileUpload: (file: File, system: AgencySystems) => Promise; +}; + +export const UploadFile: React.FC = ({ + userSystems, + setIsLoading, + setSelectedFile, + handleFileUpload, +}) => { + const dragDropAreaRef = useRef(null); + const [dragging, setDragging] = useState(false); + const acceptableFileTypes = [ + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-excel", + ]; + + const handleFileUploadAttempt = ( + e: React.ChangeEvent | DragEvent + ) => { + const files = "dataTransfer" in e ? e.dataTransfer?.files : e.target.files; + + if (!files) return; + if (!acceptableFileTypes.includes(files[0].type)) { + return showToast( + "Invalid file type. Please only upload Excel files.", + false, + "red", + 3000 + ); + } + + setIsLoading(true); + if (userSystems.length > 1) { + setIsLoading(false); + setSelectedFile(files[0]); + } else { + handleFileUpload(files[0], userSystems[0]); + } + }; + + useEffect( + () => { + const dragDropArea = dragDropAreaRef.current; + + const handleDragOver = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (!dragging) setDragging(true); + }; + + const handleDragLeave = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragging(false); + }; + + const handleDrop = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragging(false); + + if (e.dataTransfer?.files.length) { + handleFileUploadAttempt(e); + } + }; + + if (dragDropArea) { + dragDropArea.addEventListener("dragover", handleDragOver); + dragDropArea.addEventListener("dragleave", handleDragLeave); + dragDropArea.addEventListener("drop", handleDrop); + } + + return () => { + if (dragDropArea) { + dragDropArea.removeEventListener("dragover", handleDragOver); + dragDropArea.removeEventListener("dragleave", handleDragLeave); + dragDropArea.removeEventListener("drop", handleDrop); + } + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + return ( + + + {/* General Instructions */} + + + {/* System Specific Instructions */} + {userSystems?.map((system) => { + const systemName = removeSnakeCase(system).toLowerCase(); + const systemTemplate = ; + + return ( + +

{systemName}

+ {systemTemplate} +
+ ); + })} +
+ + + + + Drag & drop a file or{" "} + + { + /** reset event state to allow user to re-upload same file (re-trigger the onChange event) */ + e.currentTarget.value = ""; + }} + /> + browse your computer + + + Accepted file types: .xls, .xlsx + +
+ ); +}; diff --git a/publisher/src/components/DataUpload/UploadedFiles.tsx b/publisher/src/components/DataUpload/UploadedFiles.tsx new file mode 100644 index 000000000..f275201c4 --- /dev/null +++ b/publisher/src/components/DataUpload/UploadedFiles.tsx @@ -0,0 +1,338 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { observer } from "mobx-react-lite"; +import React, { useState } from "react"; + +import { Permission } from "../../shared/types"; +import { useStore } from "../../stores"; +import { removeSnakeCase } from "../../utils"; +import downloadIcon from "../assets/download-icon.png"; +import { Badge, BadgeColorMapping, BadgeColors } from "../Badge"; +import { Loading } from "../Loading"; +import { showToast } from "../Toast"; +import { + ActionButton, + ActionsContainer, + DownloadIcon, + ExtendedCell, + ExtendedLabelCell, + ExtendedLabelRow, + ExtendedRow, + ModalErrorWrapper, + ModalLoadingWrapper, + UploadedFile, + UploadedFileAttempt, + UploadedFilesContainer, + UploadedFilesTable, + UploadedFileStatus, +} from "."; + +export const UploadedFileRow: React.FC<{ + fileRowDetails: { + key: string; + id?: number; + selected: boolean; + name: string; + badgeColor: BadgeColors; + badgeText: string; + dateUploaded: string; + dateIngested: string; + system?: string; + uploadedBy: string; + }; + deleteUploadedFile: (spreadsheetID: number) => void; + updateUploadedFileStatus: ( + spreadsheetID: number, + status: UploadedFileStatus + ) => Promise; +}> = observer( + ({ fileRowDetails, deleteUploadedFile, updateUploadedFileStatus }) => { + const { reportStore, userStore } = useStore(); + const [isDownloading, setIsDownloading] = useState(false); + const [rowHovered, setRowHovered] = useState(false); + + const handleDownload = async (spreadsheetID: number, name: string) => { + setIsDownloading(true); + + const response = await reportStore.fetchSpreadsheetBlob(spreadsheetID); + + if (response instanceof Error) { + setIsDownloading(false); + return showToast("Failed to download. Please try again.", false, "red"); + } + + const data = await response?.blob(); + + if (data) { + const blob = new Blob([data], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;", + }); + + const link = document.createElement("a"); + const url = window.URL.createObjectURL(blob); + link.href = url; + link.download = name; + link.click(); + + window.URL.revokeObjectURL(url); + link.remove(); + setIsDownloading(false); + } + }; + + const { + id, + selected, + name, + badgeColor, + badgeText, + dateUploaded, + dateIngested, + system, + uploadedBy, + } = fileRowDetails; + + return ( + id && handleDownload(id, name)} + onMouseOver={() => setRowHovered(true)} + onMouseLeave={() => setRowHovered(false)} + > + {/* Filename */} + + {rowHovered && id && } + {name} + + {isDownloading ? "Downloading" : badgeText} + + + + {/* Date Uploaded */} + + {dateUploaded} + + + {/* Date Ingested */} + + {dateIngested} + + + {/* System */} + + {system} + + + {rowHovered && id && ( + e.stopPropagation()}> + {userStore.permissions.includes(Permission.RECIDIVIZ_ADMIN) && ( + <> + {(badgeText === "processed" || badgeText === "error") && ( + updateUploadedFileStatus(id, "UPLOADED")} + > + Mark as Pending + + )} + {(badgeText === "pending" || badgeText === "error") && ( + updateUploadedFileStatus(id, "INGESTED")} + > + Mark as Processed + + )} + {badgeText !== "error" && ( + updateUploadedFileStatus(id, "ERRORED")} + > + Mark as Error + + )} + + )} + + deleteUploadedFile(id)}> + Delete + + + )} + {/* Uploaded By */} + {uploadedBy} + + ); + } +); + +export const UploadedFiles: React.FC<{ + isLoading: boolean; + fetchError: boolean; + uploadedFiles: (UploadedFile | UploadedFileAttempt)[]; + setUploadedFiles: React.Dispatch< + React.SetStateAction<(UploadedFileAttempt | UploadedFile)[]> + >; +}> = observer(({ isLoading, fetchError, uploadedFiles, setUploadedFiles }) => { + const { reportStore } = useStore(); + const dataUploadColumnTitles = [ + "Filename", + "Date Uploaded", + "Date Ingested", + "System", + "Uploaded By", + ]; + + const isUploadedFile = ( + file: UploadedFile | UploadedFileAttempt + ): file is UploadedFile => { + return (file as UploadedFile).id !== undefined; + }; + + const uploadStatusColorMapping: BadgeColorMapping = { + UPLOADED: "ORANGE", + INGESTED: "GREEN", + ERRORED: "RED", + }; + + const translateBackendFileStatus = (status: UploadedFileStatus): string => { + if (status === "UPLOADED") return "PENDING"; + if (status === "INGESTED") return "PROCESSED"; + if (status === "ERRORED") return "ERROR"; + return status; + }; + + const getFileRowDetails = (file: UploadedFile | UploadedFileAttempt) => { + const fileStatus = file.status && translateBackendFileStatus(file.status); + + if (isUploadedFile(file)) { + const formatDate = (timestamp: number) => + Intl.DateTimeFormat("en-US", { + day: "numeric", + month: "long", + year: "numeric", + }).format(timestamp); + + return { + key: `${file.name}-${file.id}`, + id: file.id, + selected: !file.status, + name: file.name, + badgeColor: file.status + ? uploadStatusColorMapping[file.status] + : "ORANGE", + badgeText: fileStatus?.toLowerCase() || "Uploading", + dateUploaded: formatDate(file.uploaded_at), + dateIngested: file.ingested_at ? formatDate(file.ingested_at) : "--", + system: removeSnakeCase(file.system).toLowerCase(), + uploadedBy: file.uploaded_by, + }; + } + return { + key: `${file.name}-${file.upload_attempt_timestamp}`, + selected: false, + name: file.name, + badgeColor: file.status + ? uploadStatusColorMapping[file.status] + : "ORANGE", + badgeText: fileStatus?.toLowerCase() || "Uploading", + dateUploaded: "--", + dateIngested: "--", + uploadedBy: "--", + }; + }; + + const deleteUploadedFile = async (spreadsheetID: number) => { + const response = await reportStore.deleteUploadedSpreadsheet(spreadsheetID); + + if (response instanceof Error) { + return showToast(response.message, false, "red"); + } + + return setUploadedFiles((prev) => { + const filteredFiles = prev.filter( + (file) => (file as UploadedFile).id !== spreadsheetID + ); + + return filteredFiles; + }); + }; + + const updateUploadedFileStatus = async ( + spreadsheetID: number, + status: UploadedFileStatus + ) => { + const response = await reportStore.updateFileStatus(spreadsheetID, status); + + if (response instanceof Error) { + return showToast(response.message, false, "red"); + } + + return setUploadedFiles((prev) => { + const updatedFilesList = prev.map((file) => { + if (isUploadedFile(file) && file.id === spreadsheetID) { + return { ...file, status }; + } + return file; + }); + + return updatedFilesList; + }); + }; + + if (isLoading) { + return ( + + + + ); + } + + if (fetchError) { + return ( + + Failed to retrieve uploaded files. Please refresh and try again. + + ); + } + + return ( + + + + {dataUploadColumnTitles.map((title) => ( + {title} + ))} + + + {uploadedFiles.map((fileDetails) => { + const fileRowDetails = getFileRowDetails(fileDetails); + + return ( + + ); + })} + + + ); +}); diff --git a/publisher/src/components/DataUpload/index.ts b/publisher/src/components/DataUpload/index.ts new file mode 100644 index 000000000..c2a39c23d --- /dev/null +++ b/publisher/src/components/DataUpload/index.ts @@ -0,0 +1,23 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./DataUpload"; +export * from "./DataUpload.styles"; +export * from "./InstructionsTemplate"; +export * from "./SystemSelection"; +export * from "./UploadedFiles"; +export * from "./UploadFile"; diff --git a/publisher/src/components/DataViz/BarChart.tsx b/publisher/src/components/DataViz/BarChart.tsx new file mode 100644 index 000000000..23bbcca3a --- /dev/null +++ b/publisher/src/components/DataViz/BarChart.tsx @@ -0,0 +1,238 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import { + Bar, + BarChart as BarChartComponent, + CartesianGrid, + ResponsiveContainer, + Tooltip as TooltipComponent, + XAxis, + YAxis, +} from "recharts"; +import styled from "styled-components/macro"; + +import { Datapoint } from "../../shared/types"; +import { rem } from "../../utils"; +import { palette } from "../GlobalStyles"; +import Tooltip from "./Tooltip"; +import { splitUtcString } from "./utils"; + +const MAX_BAR_SIZE = 150; + +const ChartContainer = styled.div` + display: flex; + flex-grow: 1; + justify-content: center; + align-items: center; +`; + +const NoReportedData = styled.div` + position: absolute; + background: white; + padding: 8px; + + &::after { + content: "No reported data for this metric."; + } +`; + +const tickStyle = { + fontSize: rem("12px"), + fontWeight: 600, + fill: palette.solid.darkgrey, +}; + +const abbreviateNumber = (num: number) => { + // abbreviates numbers into 1k, 2.5m, 5.5t, etc + const numLength = num.toString().length; + if (numLength >= 13) { + return `${parseFloat((num / 1000000000000).toFixed(1))}t`; + } + if (numLength >= 10) { + return `${parseFloat((num / 1000000000).toFixed(1))}b`; + } + if (numLength >= 7) { + return `${parseFloat((num / 1000000).toFixed(1))}m`; + } + if (numLength >= 4) { + return `${parseFloat((num / 1000).toFixed(1))}k`; + } + return num.toString(); +}; + +interface TickProps { + y: number; + payload: { + coordinate: number; + isShow: boolean; + offset: number; + tickCoord: number; + value: number; + }; +} + +interface CustomYAxisTickProps extends TickProps { + percentageView: boolean; +} + +const CustomYAxisTick = (props: CustomYAxisTickProps) => { + const { y, payload, percentageView } = props; + const str = percentageView + ? `${payload.value * 100}%` + : abbreviateNumber(payload.value); + const label = str.length > 7 ? str.substring(0, 5).concat("...") : str; + return ( + + + {label} + + + ); +}; + +const CustomCursor = (props: React.SVGProps) => { + const { x, y, width, height } = props; + return ( + + ); +}; + +const ResponsiveBarChart: React.FC<{ + data: Datapoint[]; + dimensionNames: string[]; + percentageView?: boolean; +}> = ({ data, dimensionNames, percentageView = false }) => { + const isAnnual = data[0]?.frequency === "ANNUAL"; + const renderBarDefinitions = () => { + // each Recharts Bar component defines a category type in the stacked bar chart + const barDefinitions = []; + dimensionNames.forEach((dimension, index) => { + barDefinitions.push( + + ); + }); + barDefinitions.push( + + ); + return barDefinitions; + }; + + return ( + + + + + + + + + + + { + if (data.length === 0) { + return ""; + } + const [, , month, year] = splitUtcString(value); + return isAnnual ? year : `${month} ${year}`; + }} + tickMargin={12} + /> + ( + + )} + tickLine={false} + tickCount={percentageView ? 5 : 12} + domain={percentageView ? ["dataMin", "dataMax"] : undefined} + axisLine={false} + /> + } + content={ + + } + /> + {renderBarDefinitions()} + + + {data.length === 0 && } + + ); +}; + +export default ResponsiveBarChart; diff --git a/publisher/src/components/DataViz/DatapointsView.styles.tsx b/publisher/src/components/DataViz/DatapointsView.styles.tsx new file mode 100644 index 000000000..8c3fd8d9c --- /dev/null +++ b/publisher/src/components/DataViz/DatapointsView.styles.tsx @@ -0,0 +1,203 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { + Dropdown, + DropdownMenu, + DropdownToggle, +} from "@recidiviz/design-system"; +import React from "react"; +import styled from "styled-components/macro"; + +import { palette, typography } from "../GlobalStyles"; +import { ExtendedDropdownMenuItem } from "../Menu/Menu.styles"; + +export const DatapointsViewContainer = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; + padding: 0 15px 0 15px; +`; + +export const DatapointsViewControlsContainer = styled.div` + display: flex; + flex-direction: row; + border-bottom: 1px solid ${palette.highlight.grey9}; +`; + +const DatapointsViewDropdown = styled(Dropdown)` + display: flex; + flex-grow: 1; + flex-basis: 0; + min-width: 0; + border-left: 1px solid ${palette.highlight.grey9}; + + & > button { + transition-duration: 0ms; + border-width: 0; + } + + &:first-child { + border-left: none; + } +`; + +const DatapointsViewDropdownToggleContainer = styled(DropdownToggle)` + font-family: ${typography.family}; + padding: 0; + min-height: unset; + line-height: 0; + position: relative; + flex-grow: 1; + flex-basis: 0; + color: ${palette.solid.darkgrey}; + text-align: start; + + &:hover { + color: ${palette.solid.blue}; + } +`; +const DatapointsViewDropdownToggleSelection = styled.div` + height: 64px; + padding: 32px 32px 0px 8px; + width: 100%; + ${typography.sizeCSS.large} + color: ${palette.solid.darkgrey}; + line-height: 1.6rem; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + &:hover { + cursor: pointer; + background-color: ${palette.highlight.lightblue1}; + color: ${palette.solid.blue}; + } +`; +const DatapointsViewDropdownToggleTitle = styled.div` + padding: 8px 8px 0; + position: absolute; + ${typography.sizeCSS.small} + pointer-events: none; + top: 0; + left: 0; +`; + +const DatapointsViewDropdownToggleArrow = styled.div` + bottom: 0; + right: 0; + position: absolute; + font-size: 6px; + transform: rotate(45deg); + padding: 16px; + pointer-events: none; + + &::after { + content: "◢"; + } +`; + +interface DatapointsViewControlsDropdownMenuProps { + title: string; + selectedValue: string; +} + +const DatapointsViewControlsDropdownMenu: React.FC< + DatapointsViewControlsDropdownMenuProps +> = ({ title, selectedValue }) => ( + + + {title} + + + {selectedValue} + + + +); + +interface DatapointsViewControlsDropdownProps { + title: string; + selectedValue: string; + onSelect: (value: string) => void; + options: string[]; +} + +export const DatapointsViewControlsDropdown: React.FC< + DatapointsViewControlsDropdownProps +> = ({ title, selectedValue, onSelect, options }) => ( + + + + {options.map((value) => ( + { + onSelect(value); + }} + highlight={selectedValue === value} + > + {value} + + ))} + + +); + +export const MetricInsightsRow = styled.div` + display: flex; + flex-direction: row; + margin-top: 16px; + margin-bottom: 16px; +`; + +const MetricInsightContainer = styled.div` + margin-right: 32px; + + &:last-child { + margin-right: 0; + text-align: right; + margin-left: auto; + } +`; + +const MetricInsightTitle = styled.div` + ${typography.sizeCSS.small} + margin-bottom: 8px; +`; + +const MetricInsightValue = styled.div` + ${typography.sizeCSS.large} +`; + +interface MetricInsightProps { + title: string; + value: string; +} + +export const MetricInsight: React.FC = ({ + title, + value, +}) => ( + + {title} + {value} + +); diff --git a/publisher/src/components/DataViz/DatapointsView.tsx b/publisher/src/components/DataViz/DatapointsView.tsx new file mode 100644 index 000000000..d878a7456 --- /dev/null +++ b/publisher/src/components/DataViz/DatapointsView.tsx @@ -0,0 +1,198 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { observer } from "mobx-react-lite"; +import React, { useEffect } from "react"; + +import { + DatapointsGroupedByAggregateAndDisaggregations, + DatapointsViewSetting, + DataVizAggregateName, + DataVizTimeRangesMap, +} from "../../shared/types"; +import { useStore } from "../../stores"; +import BarChart from "./BarChart"; +import { + DatapointsViewContainer, + DatapointsViewControlsContainer, + DatapointsViewControlsDropdown, + MetricInsight, + MetricInsightsRow, +} from "./DatapointsView.styles"; +import Legend from "./Legend"; +import { + filterByTimeRange, + filterNullDatapoints, + getAverageTotalValue, + getLatestDateFormatted, + getPercentChangeOverTime, + sortDatapointDimensions, + transformData, +} from "./utils"; + +const noDisaggregationOption = "None"; + +const DatapointsView: React.FC<{ + metric: string; +}> = ({ metric }) => { + const { datapointsStore } = useStore(); + const [selectedTimeRange, setSelectedTimeRange] = + React.useState("All"); + const [selectedDisaggregation, setSelectedDisaggregation] = + React.useState(noDisaggregationOption); + const [datapointsViewSetting, setDatapointsViewSetting] = + React.useState("Count"); + + const datapointsForMetric = datapointsStore.datapointsByMetric[ + metric + ] as DatapointsGroupedByAggregateAndDisaggregations; + const data = + (selectedDisaggregation !== noDisaggregationOption && + Object.values( + datapointsForMetric.disaggregations[selectedDisaggregation] || {} + )) || + datapointsForMetric?.aggregate || + []; + const isAnnual = data[0]?.frequency === "ANNUAL"; + const disaggregationOptions = Object.keys( + datapointsStore.dimensionNamesByMetricAndDisaggregation[metric] || {} + ); + disaggregationOptions.unshift(noDisaggregationOption); + const dimensionNames = + selectedDisaggregation !== noDisaggregationOption + ? ( + datapointsStore.dimensionNamesByMetricAndDisaggregation[metric]?.[ + selectedDisaggregation + ] || [] + ) + .slice() // Must use slice() before sorting a MobX observableArray + .sort(sortDatapointDimensions) + : [DataVizAggregateName]; + + const selectedTimeRangeValue = DataVizTimeRangesMap[selectedTimeRange]; + + useEffect(() => { + if (isAnnual && selectedTimeRangeValue === 6) { + setSelectedTimeRange("All"); + } + if (!disaggregationOptions.includes(selectedDisaggregation)) { + setSelectedDisaggregation(noDisaggregationOption); + setDatapointsViewSetting("Count"); + } + if (selectedDisaggregation === noDisaggregationOption) { + setDatapointsViewSetting("Count"); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [metric]); + + const renderChartForMetric = () => { + return ( + + ); + }; + + const renderLegend = () => { + if (selectedDisaggregation !== noDisaggregationOption) { + return ; + } + return ; + }; + + const renderDataVizControls = () => { + return ( + + key !== "6 Months Ago" + ) + : Object.keys(DataVizTimeRangesMap) + } + onSelect={(key) => { + setSelectedTimeRange(key); + }} + /> + {disaggregationOptions.length > 1 && ( + { + setSelectedDisaggregation(key); + }} + /> + )} + {selectedDisaggregation !== noDisaggregationOption && ( + { + setDatapointsViewSetting(key as DatapointsViewSetting); + }} + /> + )} + + ); + }; + + const renderMetricInsightsRow = () => { + const dataSelectedInTimeRange = filterNullDatapoints( + filterByTimeRange( + datapointsForMetric?.aggregate || [], + selectedTimeRangeValue + ) + ); + const percentChange = getPercentChangeOverTime(dataSelectedInTimeRange); + const avgValue = getAverageTotalValue(dataSelectedInTimeRange, isAnnual); + + return ( + + + + + + ); + }; + + return ( + + {renderDataVizControls()} + {renderMetricInsightsRow()} + {renderChartForMetric()} + {renderLegend()} + + ); +}; + +export default observer(DatapointsView); diff --git a/publisher/src/components/DataViz/Legend.tsx b/publisher/src/components/DataViz/Legend.tsx new file mode 100644 index 000000000..0fa7b4f11 --- /dev/null +++ b/publisher/src/components/DataViz/Legend.tsx @@ -0,0 +1,78 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import styled from "styled-components/macro"; + +import { palette } from "../GlobalStyles"; + +const EmptyLegendContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + margin-bottom: 20px; + height: 50px; +`; + +const LegendContainer = styled(EmptyLegendContainer)` + border-top: 1px solid rgba(23, 28, 43, 0.6); + padding: 16px 0; + border-bottom: 1px solid rgba(23, 28, 43, 0.6); +`; + +const LegendItem = styled.div` + display: flex; + margin-right: 8px; + flex-direction: row; + align-items: center; +`; + +export const LegendColor = styled.div<{ + index: number; +}>` + width: 8px; + height: 8px; + margin-right: 4px; + background: ${({ index }) => Object.values(palette.dataViz)[index]}; + border-radius: 10px; +`; + +const LegendText = styled.div` + font-size: 12px; +`; + +const Legend: React.FC<{ + names?: string[]; +}> = ({ names }) => { + if (names) { + return ( + + {names.map((name, idx) => ( + + + {name} + + ))} + + ); + } + + return ; +}; + +export default Legend; diff --git a/publisher/src/components/DataViz/Tooltip.tsx b/publisher/src/components/DataViz/Tooltip.tsx new file mode 100644 index 000000000..08a8d4b31 --- /dev/null +++ b/publisher/src/components/DataViz/Tooltip.tsx @@ -0,0 +1,138 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import { TooltipProps as RechartsTooltipProps } from "recharts"; +import styled from "styled-components/macro"; + +import { Datapoint } from "../../shared/types"; +import { formatNumberInput } from "../../utils"; +import { palette, typography } from "../GlobalStyles"; +import { LegendColor } from "./Legend"; +import { getSumOfDimensionValues, splitUtcString } from "./utils"; + +const TooltipContainer = styled.div` + padding: 16px; + border-radius: 4px; + background: ${palette.solid.darkgrey}; +`; + +const TooltipItemContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +`; + +const TooltipName = styled.div` + ${typography.sizeCSS.normal} + flex-grow: 1; + color: ${palette.solid.white}; +`; + +const TooltipValue = styled(TooltipName)` + flex-grow: 0; + margin-left: 32px; +`; + +const TooltipNameWithBottomMargin = styled(TooltipName)` + margin-bottom: 16px; +`; + +interface TooltipProps extends RechartsTooltipProps { + percentOnly: boolean; + isAnnual: boolean; + dimensionNames: string[]; +} + +const Tooltip: React.FC = ({ + active, + payload, + label, + percentOnly, + isAnnual, + dimensionNames, +}) => { + if (active && payload && payload.length) { + const [, , month, year] = label ? splitUtcString(label) : []; + + const renderText = (val: string | number | null, maxValue: number) => { + if (typeof val !== "number") { + return "Not Reported"; + } + + let percentText = `${ + val !== 0 ? Math.round((val / maxValue) * 100) : 0 + }%`; + // handle case of non-zero being rounded down to 0% + if (percentText === "0%" && val !== 0) { + percentText = "<1%"; + } + return percentOnly + ? percentText + : `${formatNumberInput(val.toString())}${ + payload.length > 2 ? ` (${percentText})` : "" + }`; + }; + + const renderItems = () => { + if (payload.length === 0) { + return null; + } + + const datapoint = payload[0].payload as Datapoint; + if (datapoint.dataVizMissingData !== 0) { + return ( + + Not reported + + ); + } + + const sumOfDimensions = getSumOfDimensionValues(datapoint); + + return dimensionNames.map((dimension, idx: number) => { + if (dimension === "dataVizMissingData") { + return null; + } + + return ( + + + {dimension} + + {renderText(datapoint[dimension], sumOfDimensions)} + + + ); + }); + }; + + return ( + + + {isAnnual ? year : `${month} ${year}`} + + {renderItems()} + + ); + } + + return null; +}; + +export default Tooltip; diff --git a/publisher/src/components/DataViz/utils.test.ts b/publisher/src/components/DataViz/utils.test.ts new file mode 100644 index 000000000..38481aa17 --- /dev/null +++ b/publisher/src/components/DataViz/utils.test.ts @@ -0,0 +1,2066 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { Datapoint } from "../../shared/types"; +import { + fillTimeGapsBetweenDatapoints, + filterByTimeRange, + filterNullDatapoints, + incrementMonth, + incrementYear, + transformData, + transformToRelativePerchanges, +} from "./utils"; + +const testDatapoints: Datapoint[] = [ + { + start_date: "Wed, 01 Jan 2020 00:00:00 GMT", + end_date: "Sat, 01 Feb 2020 00:00:00 GMT", + Pretrial: 50515, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 45427, + "Transfer or Hold": 31375, + Unknown: 29749, + }, + { + start_date: "Sat, 01 Feb 2020 00:00:00 GMT", + end_date: "Sun, 01 Mar 2020 00:00:00 GMT", + Pretrial: 54758, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 65594, + "Transfer or Hold": 89614, + Unknown: 73426, + }, + { + start_date: "Sun, 01 Mar 2020 00:00:00 GMT", + end_date: "Wed, 01 Apr 2020 00:00:00 GMT", + Pretrial: 52304, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 94209, + "Transfer or Hold": 82736, + Unknown: 62748, + }, + { + start_date: "Wed, 01 Apr 2020 00:00:00 GMT", + end_date: "Fri, 01 May 2020 00:00:00 GMT", + Pretrial: 23335, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 90737, + "Transfer or Hold": 57573, + Unknown: 93184, + }, + { + start_date: "Fri, 01 May 2020 00:00:00 GMT", + end_date: "Mon, 01 Jun 2020 00:00:00 GMT", + Pretrial: 39489, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 27098, + "Transfer or Hold": 41196, + Unknown: 28077, + }, + { + start_date: "Mon, 01 Jun 2020 00:00:00 GMT", + end_date: "Wed, 01 Jul 2020 00:00:00 GMT", + Pretrial: 66362, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 44360, + "Transfer or Hold": 61195, + Unknown: 31909, + }, + { + start_date: "Wed, 01 Jul 2020 00:00:00 GMT", + end_date: "Sat, 01 Aug 2020 00:00:00 GMT", + Pretrial: 69380, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 53465, + "Transfer or Hold": 94375, + Unknown: 13442, + }, + { + start_date: "Sat, 01 Aug 2020 00:00:00 GMT", + end_date: "Tue, 01 Sep 2020 00:00:00 GMT", + Pretrial: 30380, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 82698, + "Transfer or Hold": 41340, + Unknown: 40324, + }, + { + start_date: "Tue, 01 Sep 2020 00:00:00 GMT", + end_date: "Thu, 01 Oct 2020 00:00:00 GMT", + Pretrial: 30338, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 74647, + "Transfer or Hold": 85087, + Unknown: 28198, + }, + { + start_date: "Thu, 01 Oct 2020 00:00:00 GMT", + end_date: "Sun, 01 Nov 2020 00:00:00 GMT", + Pretrial: 35571, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 23313, + "Transfer or Hold": 41785, + Unknown: 63556, + }, + { + start_date: "Sun, 01 Nov 2020 00:00:00 GMT", + end_date: "Tue, 01 Dec 2020 00:00:00 GMT", + Pretrial: 65779, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 53664, + "Transfer or Hold": 65962, + Unknown: 59783, + }, + { + start_date: "Tue, 01 Dec 2020 00:00:00 GMT", + end_date: "Fri, 01 Jan 2021 00:00:00 GMT", + Pretrial: 46192, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 49529, + "Transfer or Hold": 78956, + Unknown: 12664, + }, + { + start_date: "Fri, 01 Jan 2021 00:00:00 GMT", + end_date: "Mon, 01 Feb 2021 00:00:00 GMT", + Pretrial: 34311, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 24082, + "Transfer or Hold": 70165, + Unknown: 13003, + }, + { + start_date: "Mon, 01 Feb 2021 00:00:00 GMT", + end_date: "Mon, 01 Mar 2021 00:00:00 GMT", + Pretrial: 94307, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 67799, + "Transfer or Hold": 80348, + Unknown: 24894, + }, + { + start_date: "Mon, 01 Mar 2021 00:00:00 GMT", + end_date: "Thu, 01 Apr 2021 00:00:00 GMT", + Pretrial: 55116, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 65802, + "Transfer or Hold": 46493, + Unknown: 47052, + }, + { + start_date: "Thu, 01 Apr 2021 00:00:00 GMT", + end_date: "Sat, 01 May 2021 00:00:00 GMT", + Pretrial: 60342, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 27224, + "Transfer or Hold": 14822, + Unknown: 35285, + }, + { + start_date: "Sat, 01 May 2021 00:00:00 GMT", + end_date: "Tue, 01 Jun 2021 00:00:00 GMT", + Pretrial: 76072, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 40997, + "Transfer or Hold": 84448, + Unknown: 86808, + }, + { + start_date: "Tue, 01 Jun 2021 00:00:00 GMT", + end_date: "Thu, 01 Jul 2021 00:00:00 GMT", + Pretrial: 55707, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 21701, + "Transfer or Hold": 97274, + Unknown: 70005, + }, + { + start_date: "Thu, 01 Jul 2021 00:00:00 GMT", + end_date: "Sun, 01 Aug 2021 00:00:00 GMT", + "Transfer or Hold": 92055, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 64106, + Pretrial: 19694, + Unknown: 51952, + }, + { + start_date: "Sun, 01 Aug 2021 00:00:00 GMT", + end_date: "Wed, 01 Sep 2021 00:00:00 GMT", + Pretrial: 19163, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 95457, + "Transfer or Hold": 50399, + Unknown: 37598, + }, + { + start_date: "Wed, 01 Sep 2021 00:00:00 GMT", + end_date: "Fri, 01 Oct 2021 00:00:00 GMT", + Pretrial: 56132, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 49555, + "Transfer or Hold": 67307, + Unknown: 16254, + }, + { + start_date: "Fri, 01 Oct 2021 00:00:00 GMT", + end_date: "Mon, 01 Nov 2021 00:00:00 GMT", + Pretrial: 74036, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 53503, + "Transfer or Hold": 29868, + Unknown: 94671, + }, + { + start_date: "Mon, 01 Nov 2021 00:00:00 GMT", + end_date: "Wed, 01 Dec 2021 00:00:00 GMT", + Sentenced: 34289, + frequency: "MONTHLY", + dataVizMissingData: 0, + Pretrial: 30696, + "Transfer or Hold": 56361, + Unknown: 18829, + }, + { + start_date: "Wed, 01 Dec 2021 00:00:00 GMT", + end_date: "Sat, 01 Jan 2022 00:00:00 GMT", + Pretrial: 21968, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 47320, + "Transfer or Hold": 43536, + Unknown: 71164, + }, + { + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Tue, 01 Feb 2022 00:00:00 GMT", + Pretrial: 38210, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 70537, + "Transfer or Hold": 87658, + Unknown: 84018, + }, + { + start_date: "Tue, 01 Feb 2022 00:00:00 GMT", + end_date: "Tue, 01 Mar 2022 00:00:00 GMT", + Pretrial: 37196, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 34013, + "Transfer or Hold": 35342, + Unknown: 16376, + }, + { + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + Pretrial: 13935, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 28989, + "Transfer or Hold": 56841, + Unknown: 52659, + }, + { + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + Pretrial: 54440, + frequency: "MONTHLY", + dataVizMissingData: 0, + Unknown: 55161, + "Transfer or Hold": 17782, + Sentenced: 90906, + }, + { + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + Pretrial: 49829, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 85046, + "Transfer or Hold": 60630, + Unknown: 10732, + }, + { + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + Pretrial: 15321, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 68658, + "Transfer or Hold": 73603, + Unknown: 72871, + }, + { + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + Pretrial: 61998, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 29566, + "Transfer or Hold": 98614, + Unknown: 63892, + }, + { + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + Pretrial: 49052, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 19779, + "Transfer or Hold": 38057, + Unknown: 49554, + }, + { + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + Sentenced: 52422, + frequency: "MONTHLY", + dataVizMissingData: 0, + Pretrial: 34244, + "Transfer or Hold": 62878, + Unknown: 48777, + }, + { + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + Pretrial: 67196, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 54738, + "Transfer or Hold": 95846, + Unknown: 94767, + }, + { + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + Pretrial: 76254, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 63070, + "Transfer or Hold": 25465, + Unknown: 81989, + }, + { + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + Unknown: 29537, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 52722, + Sentenced: 84901, + Pretrial: 34694, + }, +]; + +const testDatapoints2: Datapoint[] = [ + { + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 97164, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 36671, + Black: 26028, + "External / Unknown": 47948, + Hispanic: 57558, + "Native Hawaiian / Pacific Islander": 90632, + Other: 16338, + White: 22298, + }, + { + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + Hispanic: 66829, + frequency: "MONTHLY", + dataVizMissingData: 0, + "External / Unknown": 97373, + Black: 20261, + Asian: 63835, + "American Indian / Alaskan Native": 31596, + "Native Hawaiian / Pacific Islander": 82033, + Other: 29044, + White: 96511, + }, + { + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 79637, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 14139, + Black: 85121, + "External / Unknown": 38446, + Hispanic: 31772, + "Native Hawaiian / Pacific Islander": 88002, + Other: 33259, + White: 70561, + }, + { + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 45039, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 65632, + Black: 39540, + "External / Unknown": 16119, + Hispanic: 14102, + "Native Hawaiian / Pacific Islander": 52909, + Other: 60103, + White: 73688, + }, + { + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 80150, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 74203, + Black: 23688, + "External / Unknown": 44627, + Hispanic: 65335, + "Native Hawaiian / Pacific Islander": 56843, + Other: 58110, + White: 62313, + }, + { + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 21221, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 36873, + Black: 30958, + "External / Unknown": 40857, + Hispanic: 85505, + "Native Hawaiian / Pacific Islander": 66954, + Other: 93569, + White: 52647, + }, + { + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + Other: 69493, + frequency: "MONTHLY", + dataVizMissingData: 0, + White: 37434, + "Native Hawaiian / Pacific Islander": 87941, + "American Indian / Alaskan Native": 55887, + Asian: 69104, + Black: 73150, + "External / Unknown": 64346, + Hispanic: 33261, + }, + { + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 69650, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 24615, + Black: 55423, + "External / Unknown": 98968, + Hispanic: 68576, + "Native Hawaiian / Pacific Islander": 94690, + Other: 64054, + White: 81617, + }, + { + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 17038, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 44387, + Black: 51777, + "External / Unknown": 54533, + Hispanic: 16689, + "Native Hawaiian / Pacific Islander": 69182, + Other: 99842, + White: 29578, + }, + { + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + "Native Hawaiian / Pacific Islander": 89112, + frequency: "MONTHLY", + dataVizMissingData: 0, + Other: 37341, + White: 59971, + "American Indian / Alaskan Native": 18236, + Asian: 60388, + Black: 35380, + "External / Unknown": 36941, + Hispanic: 90445, + }, +]; + +const testDatapoints2Percentages: Datapoint[] = [ + { + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.24621107498790026, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.09292337008440668, + Black: 0.06595428203640308, + "External / Unknown": 0.12149899781318022, + Hispanic: 0.145850490450718, + "Native Hawaiian / Pacific Islander": 0.22965915512230226, + Other: 0.04140007145807413, + White: 0.05650255804701536, + }, + { + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + Hispanic: 0.13709018999675884, + frequency: "MONTHLY", + dataVizMissingData: 0, + "External / Unknown": 0.1997468624482545, + Black: 0.04156256025863519, + Asian: 0.1309484247623502, + "American Indian / Alaskan Native": 0.06481470085049294, + "Native Hawaiian / Pacific Islander": 0.16827903389253346, + Other: 0.059579635760910146, + White: 0.1979785920300647, + }, + { + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.18060856766386127, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.03206580531912722, + Black: 0.19304571854936192, + "External / Unknown": 0.08719159426403318, + Hispanic: 0.07205564513751397, + "Native Hawaiian / Pacific Islander": 0.19957953176984466, + Other: 0.07542800898994641, + White: 0.16002512830631133, + }, + { + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.1226779468964841, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.1787694889031738, + Black: 0.10769968294782258, + "External / Unknown": 0.04390518941416166, + Hispanic: 0.0384112526284824, + "Native Hawaiian / Pacific Islander": 0.1441143784796749, + Other: 0.16370951047579618, + White: 0.2007125502544044, + }, + { + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.17226593647975688, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.1594840834012152, + Black: 0.0509124828862443, + "External / Unknown": 0.09591655579890343, + Hispanic: 0.14042414173306195, + "Native Hawaiian / Pacific Islander": 0.12217233471389669, + Other: 0.12489549056567276, + White: 0.1339289744212488, + }, + { + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.04951421424971534, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.08603447632202789, + Black: 0.0722332144923749, + "External / Unknown": 0.09533020364735968, + Hispanic: 0.1995058144961081, + "Native Hawaiian / Pacific Islander": 0.15622141750508653, + Other: 0.21832126257629778, + White: 0.12283939671102981, + }, + { + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + Other: 0.14164438175681185, + frequency: "MONTHLY", + dataVizMissingData: 0, + White: 0.07629999836939684, + "Native Hawaiian / Pacific Islander": 0.17924609062892363, + "American Indian / Alaskan Native": 0.11391189851125931, + Asian: 0.14085150097020888, + Black: 0.14909827645245977, + "External / Unknown": 0.13115348867546106, + Hispanic: 0.06779436463547867, + }, + { + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.12491189810489013, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.04414510225200101, + Black: 0.09939687191195011, + "External / Unknown": 0.1774914677910232, + Hispanic: 0.12298576201638112, + "Native Hawaiian / Pacific Islander": 0.16981920504740913, + Other: 0.11487590410926966, + White: 0.14637378876707563, + }, + { + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + "American Indian / Alaskan Native": 0.044482619978800396, + frequency: "MONTHLY", + dataVizMissingData: 0, + Asian: 0.11588508351913447, + Black: 0.13517881292653763, + "External / Unknown": 0.14237414692475184, + Hispanic: 0.04357145467931681, + "Native Hawaiian / Pacific Islander": 0.18061959240364883, + Other: 0.2606663777393702, + White: 0.07722191182843985, + }, + { + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + "Native Hawaiian / Pacific Islander": 0.20829612869144068, + frequency: "MONTHLY", + dataVizMissingData: 0, + Other: 0.08728325861238763, + White: 0.14018007825830853, + "American Indian / Alaskan Native": 0.04262600101913448, + Asian: 0.14115480091815602, + Black: 0.08269949090025104, + "External / Unknown": 0.0863482728475459, + Hispanic: 0.21141196875277574, + }, +]; + +const testDatapoints3: Datapoint[] = [ + { + Total: 28293, + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 56673, + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 59646, + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 95570, + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 23877, + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 42551, + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 77484, + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: null, + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 12312, + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 0, + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, +]; + +const testDatapoints3WithoutNullDatapoints: Datapoint[] = [ + { + Total: 28293, + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 56673, + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 59646, + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 95570, + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 23877, + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 42551, + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 77484, + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 12312, + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, + { + Total: 0, + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + frequency: "MONTHLY", + dataVizMissingData: 0, + }, +]; + +const testDatapoints4: Datapoint[] = [ + { + Total: 52342, + start_date: "Thu, 01 Jan 2015 00:00:00 GMT", + end_date: "Fri, 01 Jan 2016 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + Total: 41241, + start_date: "Tue, 01 Jan 2019 00:00:00 GMT", + end_date: "Wed, 01 Jan 2020 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + Total: 74435, + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, +]; + +const testDatapoints4WithGapDatapoints: Datapoint[] = [ + { + Total: 52342, + start_date: "Thu, 01 Jan 2015 00:00:00 GMT", + end_date: "Fri, 01 Jan 2016 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + start_date: "Fri, 01 Jan 2016 00:00:00 GMT", + end_date: "Sun, 01 Jan 2017 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Sun, 01 Jan 2017 00:00:00 GMT", + end_date: "Mon, 01 Jan 2018 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Mon, 01 Jan 2018 00:00:00 GMT", + end_date: "Tue, 01 Jan 2019 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + Total: 41241, + start_date: "Tue, 01 Jan 2019 00:00:00 GMT", + end_date: "Wed, 01 Jan 2020 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + start_date: "Wed, 01 Jan 2020 00:00:00 GMT", + end_date: "Fri, 01 Jan 2021 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Fri, 01 Jan 2021 00:00:00 GMT", + end_date: "Sat, 01 Jan 2022 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + Total: 74435, + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, +]; + +const testDatapoints4WithGapDatapoints2: Datapoint[] = [ + { + start_date: "Tue, 01 Jan 2013 00:00:00 GMT", + end_date: "Wed, 01 Jan 2014 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Wed, 01 Jan 2014 00:00:00 GMT", + end_date: "Thu, 01 Jan 2015 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + Total: 52342, + start_date: "Thu, 01 Jan 2015 00:00:00 GMT", + end_date: "Fri, 01 Jan 2016 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + start_date: "Fri, 01 Jan 2016 00:00:00 GMT", + end_date: "Sun, 01 Jan 2017 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Sun, 01 Jan 2017 00:00:00 GMT", + end_date: "Mon, 01 Jan 2018 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Mon, 01 Jan 2018 00:00:00 GMT", + end_date: "Tue, 01 Jan 2019 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + Total: 41241, + start_date: "Tue, 01 Jan 2019 00:00:00 GMT", + end_date: "Wed, 01 Jan 2020 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, + { + start_date: "Wed, 01 Jan 2020 00:00:00 GMT", + end_date: "Fri, 01 Jan 2021 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + start_date: "Fri, 01 Jan 2021 00:00:00 GMT", + end_date: "Sat, 01 Jan 2022 00:00:00 GMT", + dataVizMissingData: 24811.666666666668, + frequency: "ANNUAL", + Total: 0, + }, + { + Total: 74435, + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + frequency: "ANNUAL", + dataVizMissingData: 0, + }, +]; + +const testDatapoints5: Datapoint[] = [ + { + start_date: "Thu, 01 Aug 2019 00:00:00 GMT", + end_date: "Sun, 01 Sep 2019 00:00:00 GMT", + Pretrial: 41, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 32, + "Transfer or Hold": 53, + Unknown: 12, + }, + { + start_date: "Wed, 01 Jan 2020 00:00:00 GMT", + end_date: "Sat, 01 Feb 2020 00:00:00 GMT", + Pretrial: 36541, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 79028, + Unknown: 65749, + "Transfer or Hold": 48334, + }, + { + start_date: "Sat, 01 Feb 2020 00:00:00 GMT", + end_date: "Sun, 01 Mar 2020 00:00:00 GMT", + Pretrial: 24112, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 82706, + "Transfer or Hold": 83057, + Unknown: 17415, + }, + { + start_date: "Sun, 01 Mar 2020 00:00:00 GMT", + end_date: "Wed, 01 Apr 2020 00:00:00 GMT", + Pretrial: 23767, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 90978, + "Transfer or Hold": 82590, + Unknown: 35303, + }, + { + start_date: "Wed, 01 Apr 2020 00:00:00 GMT", + end_date: "Fri, 01 May 2020 00:00:00 GMT", + Pretrial: 78458, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 46769, + "Transfer or Hold": 64874, + Unknown: 38205, + }, + { + start_date: "Fri, 01 May 2020 00:00:00 GMT", + end_date: "Mon, 01 Jun 2020 00:00:00 GMT", + Pretrial: 22677, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 61323, + "Transfer or Hold": 72938, + Unknown: 29553, + }, + { + start_date: "Mon, 01 Jun 2020 00:00:00 GMT", + end_date: "Wed, 01 Jul 2020 00:00:00 GMT", + Pretrial: 88997, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 38048, + "Transfer or Hold": 84709, + Unknown: 62951, + }, + { + start_date: "Wed, 01 Jul 2020 00:00:00 GMT", + end_date: "Sat, 01 Aug 2020 00:00:00 GMT", + Pretrial: 16324, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 16996, + "Transfer or Hold": 63347, + Unknown: 91685, + }, + { + start_date: "Sat, 01 Aug 2020 00:00:00 GMT", + end_date: "Tue, 01 Sep 2020 00:00:00 GMT", + Pretrial: 46650, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 23200, + "Transfer or Hold": 24195, + Unknown: 33681, + }, + { + start_date: "Tue, 01 Sep 2020 00:00:00 GMT", + end_date: "Thu, 01 Oct 2020 00:00:00 GMT", + Pretrial: 82010, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 14841, + "Transfer or Hold": 82407, + Unknown: 97055, + }, + { + start_date: "Thu, 01 Oct 2020 00:00:00 GMT", + end_date: "Sun, 01 Nov 2020 00:00:00 GMT", + Sentenced: 37221, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 22512, + Unknown: 49751, + Pretrial: 51938, + }, + { + start_date: "Sun, 01 Nov 2020 00:00:00 GMT", + end_date: "Tue, 01 Dec 2020 00:00:00 GMT", + Pretrial: 13252, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 89151, + "Transfer or Hold": 31343, + Unknown: 17920, + }, + { + start_date: "Tue, 01 Dec 2020 00:00:00 GMT", + end_date: "Fri, 01 Jan 2021 00:00:00 GMT", + Pretrial: 42300, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 27629, + "Transfer or Hold": 11736, + Unknown: 13635, + }, + { + start_date: "Fri, 01 Jan 2021 00:00:00 GMT", + end_date: "Mon, 01 Feb 2021 00:00:00 GMT", + Pretrial: 98369, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 46574, + "Transfer or Hold": 21353, + Unknown: 88769, + }, + { + start_date: "Mon, 01 Feb 2021 00:00:00 GMT", + end_date: "Mon, 01 Mar 2021 00:00:00 GMT", + Pretrial: 78759, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 53386, + "Transfer or Hold": 59075, + Unknown: 27134, + }, + { + start_date: "Mon, 01 Mar 2021 00:00:00 GMT", + end_date: "Thu, 01 Apr 2021 00:00:00 GMT", + Pretrial: 60053, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 34197, + "Transfer or Hold": 63531, + Unknown: 83579, + }, + { + start_date: "Thu, 01 Apr 2021 00:00:00 GMT", + end_date: "Sat, 01 May 2021 00:00:00 GMT", + Pretrial: 44759, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 17229, + "Transfer or Hold": 77299, + Unknown: 58031, + }, + { + start_date: "Sat, 01 May 2021 00:00:00 GMT", + end_date: "Tue, 01 Jun 2021 00:00:00 GMT", + Pretrial: 61558, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 59243, + "Transfer or Hold": 46498, + Unknown: 53175, + }, + { + start_date: "Tue, 01 Jun 2021 00:00:00 GMT", + end_date: "Thu, 01 Jul 2021 00:00:00 GMT", + Pretrial: 73726, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 60098, + "Transfer or Hold": 24792, + Unknown: 90376, + }, + { + start_date: "Thu, 01 Jul 2021 00:00:00 GMT", + end_date: "Sun, 01 Aug 2021 00:00:00 GMT", + Pretrial: 31102, + frequency: "MONTHLY", + dataVizMissingData: 0, + Unknown: 78794, + "Transfer or Hold": 10572, + Sentenced: 30228, + }, + { + start_date: "Sun, 01 Aug 2021 00:00:00 GMT", + end_date: "Wed, 01 Sep 2021 00:00:00 GMT", + Pretrial: 99665, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 82644, + "Transfer or Hold": 58060, + Unknown: 72892, + }, + { + start_date: "Wed, 01 Sep 2021 00:00:00 GMT", + end_date: "Fri, 01 Oct 2021 00:00:00 GMT", + Pretrial: 46416, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 33763, + "Transfer or Hold": 10680, + Unknown: 94129, + }, + { + start_date: "Fri, 01 Oct 2021 00:00:00 GMT", + end_date: "Mon, 01 Nov 2021 00:00:00 GMT", + Pretrial: 80719, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 96265, + "Transfer or Hold": 12347, + Unknown: 18056, + }, + { + start_date: "Mon, 01 Nov 2021 00:00:00 GMT", + end_date: "Wed, 01 Dec 2021 00:00:00 GMT", + Pretrial: 83549, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 77473, + "Transfer or Hold": 49179, + Unknown: 78634, + }, + { + start_date: "Wed, 01 Dec 2021 00:00:00 GMT", + end_date: "Sat, 01 Jan 2022 00:00:00 GMT", + Sentenced: 52931, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 24687, + Unknown: 92620, + Pretrial: 40598, + }, + { + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Tue, 01 Feb 2022 00:00:00 GMT", + Pretrial: 75641, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 59666, + "Transfer or Hold": 17668, + Unknown: 92062, + }, + { + start_date: "Tue, 01 Feb 2022 00:00:00 GMT", + end_date: "Tue, 01 Mar 2022 00:00:00 GMT", + Pretrial: 97743, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 34823, + "Transfer or Hold": 37207, + Unknown: 18960, + }, + { + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + Pretrial: 80843, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 92536, + "Transfer or Hold": 56184, + Unknown: 51573, + }, + { + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + Pretrial: 10958, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 48931, + "Transfer or Hold": 46672, + Unknown: 98907, + }, + { + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + Pretrial: 63710, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 26703, + "Transfer or Hold": 72473, + Unknown: 85367, + }, + { + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + Pretrial: 32303, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 85419, + "Transfer or Hold": 98064, + Unknown: 95473, + }, + { + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + Unknown: 90809, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 18233, + Sentenced: 38446, + Pretrial: 60273, + }, + { + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + Pretrial: 51711, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 25203, + "Transfer or Hold": 12208, + Unknown: 37137, + }, + { + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + Pretrial: 92358, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 99972, + "Transfer or Hold": 54671, + Unknown: 49974, + }, + { + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + Pretrial: null, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: null, + "Transfer or Hold": null, + Unknown: null, + }, + { + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + Unknown: 4000, + frequency: "MONTHLY", + dataVizMissingData: 0, + Pretrial: null, + Sentenced: null, + "Transfer or Hold": 1, + }, + { + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + Pretrial: null, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: null, + "Transfer or Hold": 0, + Unknown: null, + }, +]; + +const testDatapoints5Transformed: Datapoint[] = [ + { + start_date: "Fri, 01 Sep 2017 00:00:00 GMT", + end_date: "Sun, 01 Oct 2017 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sun, 01 Oct 2017 00:00:00 GMT", + end_date: "Wed, 01 Nov 2017 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Wed, 01 Nov 2017 00:00:00 GMT", + end_date: "Fri, 01 Dec 2017 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Fri, 01 Dec 2017 00:00:00 GMT", + end_date: "Mon, 01 Jan 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Mon, 01 Jan 2018 00:00:00 GMT", + end_date: "Thu, 01 Feb 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Thu, 01 Feb 2018 00:00:00 GMT", + end_date: "Thu, 01 Mar 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Thu, 01 Mar 2018 00:00:00 GMT", + end_date: "Sun, 01 Apr 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sun, 01 Apr 2018 00:00:00 GMT", + end_date: "Tue, 01 May 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Tue, 01 May 2018 00:00:00 GMT", + end_date: "Fri, 01 Jun 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Fri, 01 Jun 2018 00:00:00 GMT", + end_date: "Sun, 01 Jul 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sun, 01 Jul 2018 00:00:00 GMT", + end_date: "Wed, 01 Aug 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Wed, 01 Aug 2018 00:00:00 GMT", + end_date: "Sat, 01 Sep 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sat, 01 Sep 2018 00:00:00 GMT", + end_date: "Mon, 01 Oct 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Mon, 01 Oct 2018 00:00:00 GMT", + end_date: "Thu, 01 Nov 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Thu, 01 Nov 2018 00:00:00 GMT", + end_date: "Sat, 01 Dec 2018 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sat, 01 Dec 2018 00:00:00 GMT", + end_date: "Tue, 01 Jan 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Tue, 01 Jan 2019 00:00:00 GMT", + end_date: "Fri, 01 Feb 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Fri, 01 Feb 2019 00:00:00 GMT", + end_date: "Fri, 01 Mar 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Fri, 01 Mar 2019 00:00:00 GMT", + end_date: "Mon, 01 Apr 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Mon, 01 Apr 2019 00:00:00 GMT", + end_date: "Wed, 01 May 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Wed, 01 May 2019 00:00:00 GMT", + end_date: "Sat, 01 Jun 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sat, 01 Jun 2019 00:00:00 GMT", + end_date: "Mon, 01 Jul 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Mon, 01 Jul 2019 00:00:00 GMT", + end_date: "Thu, 01 Aug 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Thu, 01 Aug 2019 00:00:00 GMT", + end_date: "Sun, 01 Sep 2019 00:00:00 GMT", + Pretrial: 0.2971014492753623, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.2318840579710145, + "Transfer or Hold": 0.38405797101449274, + Unknown: 0.08695652173913043, + }, + { + start_date: "Sun, 01 Sep 2019 00:00:00 GMT", + end_date: "Tue, 01 Oct 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Tue, 01 Oct 2019 00:00:00 GMT", + end_date: "Fri, 01 Nov 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Fri, 01 Nov 2019 00:00:00 GMT", + end_date: "Sun, 01 Dec 2019 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Sun, 01 Dec 2019 00:00:00 GMT", + end_date: "Wed, 01 Jan 2020 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Wed, 01 Jan 2020 00:00:00 GMT", + end_date: "Sat, 01 Feb 2020 00:00:00 GMT", + Pretrial: 0.15911466044275688, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.3441206695347744, + Unknown: 0.2862983993172278, + "Transfer or Hold": 0.21046627070524096, + }, + { + start_date: "Sat, 01 Feb 2020 00:00:00 GMT", + end_date: "Sun, 01 Mar 2020 00:00:00 GMT", + Pretrial: 0.11632013121713541, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.3989869265280525, + "Transfer or Hold": 0.4006802064740219, + Unknown: 0.0840127357807902, + }, + { + start_date: "Sun, 01 Mar 2020 00:00:00 GMT", + end_date: "Wed, 01 Apr 2020 00:00:00 GMT", + Pretrial: 0.10216301721988669, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.39107110618213703, + "Transfer or Hold": 0.355015087818843, + Unknown: 0.15175078877913326, + }, + { + start_date: "Wed, 01 Apr 2020 00:00:00 GMT", + end_date: "Fri, 01 May 2020 00:00:00 GMT", + Pretrial: 0.343652816833548, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.204852259686561, + "Transfer or Hold": 0.28415372351142765, + Unknown: 0.1673411999684634, + }, + { + start_date: "Fri, 01 May 2020 00:00:00 GMT", + end_date: "Mon, 01 Jun 2020 00:00:00 GMT", + Pretrial: 0.12159836131502325, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.32882551973017465, + "Transfer or Hold": 0.39110734566279337, + Unknown: 0.15846877329200873, + }, + { + start_date: "Mon, 01 Jun 2020 00:00:00 GMT", + end_date: "Wed, 01 Jul 2020 00:00:00 GMT", + Pretrial: 0.32397298920660345, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.13850494166469485, + "Transfer or Hold": 0.3083635172275714, + Unknown: 0.2291585519011303, + }, + { + start_date: "Wed, 01 Jul 2020 00:00:00 GMT", + end_date: "Sat, 01 Aug 2020 00:00:00 GMT", + Pretrial: 0.0866675161399932, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.0902353041114509, + "Transfer or Hold": 0.33632241760108733, + Unknown: 0.48677476214746856, + }, + { + start_date: "Sat, 01 Aug 2020 00:00:00 GMT", + end_date: "Tue, 01 Sep 2020 00:00:00 GMT", + Pretrial: 0.36523495607785417, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.1816388206003476, + "Transfer or Hold": 0.1894289338114401, + Unknown: 0.2636972895103581, + }, + { + start_date: "Tue, 01 Sep 2020 00:00:00 GMT", + end_date: "Thu, 01 Oct 2020 00:00:00 GMT", + Pretrial: 0.296801091515781, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.05371082793788204, + "Transfer or Hold": 0.29823786792514284, + Unknown: 0.3512502126211941, + }, + { + start_date: "Thu, 01 Oct 2020 00:00:00 GMT", + end_date: "Sun, 01 Nov 2020 00:00:00 GMT", + Sentenced: 0.23058195289365763, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 0.1394605444115424, + Unknown: 0.30820458177943527, + Pretrial: 0.32175292091536467, + }, + { + start_date: "Sun, 01 Nov 2020 00:00:00 GMT", + end_date: "Tue, 01 Dec 2020 00:00:00 GMT", + Pretrial: 0.08737620824706922, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.5878113749950549, + "Transfer or Hold": 0.2066580512441813, + Unknown: 0.11815436551369457, + }, + { + start_date: "Tue, 01 Dec 2020 00:00:00 GMT", + end_date: "Fri, 01 Jan 2021 00:00:00 GMT", + Pretrial: 0.4438614900314795, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.28991605456453307, + "Transfer or Hold": 0.1231479538300105, + Unknown: 0.14307450157397691, + }, + { + start_date: "Fri, 01 Jan 2021 00:00:00 GMT", + end_date: "Mon, 01 Feb 2021 00:00:00 GMT", + Pretrial: 0.38566247819183347, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.18259659302530729, + "Transfer or Hold": 0.08371591555093799, + Unknown: 0.34802501323192125, + }, + { + start_date: "Mon, 01 Feb 2021 00:00:00 GMT", + end_date: "Mon, 01 Mar 2021 00:00:00 GMT", + Pretrial: 0.3606941022376508, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.24449288769612648, + "Transfer or Hold": 0.2705469100634749, + Unknown: 0.12426610000274783, + }, + { + start_date: "Mon, 01 Mar 2021 00:00:00 GMT", + end_date: "Thu, 01 Apr 2021 00:00:00 GMT", + Pretrial: 0.2488109048723898, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.14168462048392444, + "Transfer or Hold": 0.26322091481604243, + Unknown: 0.34628355982764336, + }, + { + start_date: "Thu, 01 Apr 2021 00:00:00 GMT", + end_date: "Sat, 01 May 2021 00:00:00 GMT", + Pretrial: 0.22683688259560708, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.08731590630353034, + "Transfer or Hold": 0.39174834531061536, + Unknown: 0.2940988657902472, + }, + { + start_date: "Sat, 01 May 2021 00:00:00 GMT", + end_date: "Tue, 01 Jun 2021 00:00:00 GMT", + Pretrial: 0.2792075256039261, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.2687074212832352, + "Transfer or Hold": 0.21090015149178587, + Unknown: 0.24118490162105283, + }, + { + start_date: "Tue, 01 Jun 2021 00:00:00 GMT", + end_date: "Thu, 01 Jul 2021 00:00:00 GMT", + Pretrial: 0.29609786659812365, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.24136518442359595, + "Transfer or Hold": 0.0995694640791672, + Unknown: 0.36296748489911324, + }, + { + start_date: "Thu, 01 Jul 2021 00:00:00 GMT", + end_date: "Sun, 01 Aug 2021 00:00:00 GMT", + Pretrial: 0.2063890216064129, + frequency: "MONTHLY", + dataVizMissingData: 0, + Unknown: 0.5228672293889686, + "Transfer or Hold": 0.07015448319796146, + Sentenced: 0.20058926580665712, + }, + { + start_date: "Sun, 01 Aug 2021 00:00:00 GMT", + end_date: "Wed, 01 Sep 2021 00:00:00 GMT", + Pretrial: 0.3181532332463984, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.26381834955516326, + "Transfer or Hold": 0.18534065842859468, + Unknown: 0.23268775876984368, + }, + { + start_date: "Wed, 01 Sep 2021 00:00:00 GMT", + end_date: "Fri, 01 Oct 2021 00:00:00 GMT", + Pretrial: 0.25091357277228793, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.1825145414837719, + "Transfer or Hold": 0.05773347460375808, + Unknown: 0.508838411140182, + }, + { + start_date: "Fri, 01 Oct 2021 00:00:00 GMT", + end_date: "Mon, 01 Nov 2021 00:00:00 GMT", + Pretrial: 0.3892191892452275, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.4641804934735543, + "Transfer or Hold": 0.059536036492162, + Unknown: 0.0870642807890562, + }, + { + start_date: "Mon, 01 Nov 2021 00:00:00 GMT", + end_date: "Wed, 01 Dec 2021 00:00:00 GMT", + Pretrial: 0.2892620354181453, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.2682258036595288, + "Transfer or Hold": 0.17026676129970397, + Unknown: 0.2722453996226219, + }, + { + start_date: "Wed, 01 Dec 2021 00:00:00 GMT", + end_date: "Sat, 01 Jan 2022 00:00:00 GMT", + Sentenced: 0.2510529511089188, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 0.11709100912557628, + Unknown: 0.43929879147773626, + Pretrial: 0.19255724828776868, + }, + { + start_date: "Sat, 01 Jan 2022 00:00:00 GMT", + end_date: "Tue, 01 Feb 2022 00:00:00 GMT", + Pretrial: 0.30869215669470323, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.24349792072217666, + "Transfer or Hold": 0.0721033966298967, + Unknown: 0.3757065259532234, + }, + { + start_date: "Tue, 01 Feb 2022 00:00:00 GMT", + end_date: "Tue, 01 Mar 2022 00:00:00 GMT", + Pretrial: 0.5178903530384193, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.1845093332909454, + "Transfer or Hold": 0.19714093454774734, + Unknown: 0.1004593791228879, + }, + { + start_date: "Tue, 01 Mar 2022 00:00:00 GMT", + end_date: "Fri, 01 Apr 2022 00:00:00 GMT", + Pretrial: 0.28755833475613224, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.32915030447897103, + "Transfer or Hold": 0.19984633771555405, + Unknown: 0.18344502304934265, + }, + { + start_date: "Fri, 01 Apr 2022 00:00:00 GMT", + end_date: "Sun, 01 May 2022 00:00:00 GMT", + Pretrial: 0.05333190569821091, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.23814413923335995, + "Transfer or Hold": 0.22714972647808904, + Unknown: 0.4813742285903401, + }, + { + start_date: "Sun, 01 May 2022 00:00:00 GMT", + end_date: "Wed, 01 Jun 2022 00:00:00 GMT", + Pretrial: 0.25663335387689173, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.10756365481988132, + "Transfer or Hold": 0.2919320209624858, + Unknown: 0.3438709703407411, + }, + { + start_date: "Wed, 01 Jun 2022 00:00:00 GMT", + end_date: "Fri, 01 Jul 2022 00:00:00 GMT", + Pretrial: 0.10378173803809689, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.27443061887367115, + "Transfer or Hold": 0.3150559501893921, + Unknown: 0.3067316928988399, + }, + { + start_date: "Fri, 01 Jul 2022 00:00:00 GMT", + end_date: "Mon, 01 Aug 2022 00:00:00 GMT", + Unknown: 0.437083957046799, + frequency: "MONTHLY", + dataVizMissingData: 0, + "Transfer or Hold": 0.08775949287883673, + Sentenced: 0.1850491670717796, + Pretrial: 0.2901073830025847, + }, + { + start_date: "Mon, 01 Aug 2022 00:00:00 GMT", + end_date: "Thu, 01 Sep 2022 00:00:00 GMT", + Pretrial: 0.4095628826459896, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.19961349289951608, + "Transfer or Hold": 0.09669013694073293, + Unknown: 0.2941334875137614, + }, + { + start_date: "Thu, 01 Sep 2022 00:00:00 GMT", + end_date: "Sat, 01 Oct 2022 00:00:00 GMT", + Pretrial: 0.3109958750736594, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: 0.3366343968347504, + "Transfer or Hold": 0.18409293711591884, + Unknown: 0.16827679097567136, + }, + { + start_date: "Sat, 01 Oct 2022 00:00:00 GMT", + end_date: "Tue, 01 Nov 2022 00:00:00 GMT", + dataVizMissingData: 0.3333333333333333, + frequency: "MONTHLY", + Pretrial: 0, + Sentenced: 0, + "Transfer or Hold": 0, + Unknown: 0, + }, + { + start_date: "Tue, 01 Nov 2022 00:00:00 GMT", + end_date: "Thu, 01 Dec 2022 00:00:00 GMT", + Unknown: 0.9997500624843789, + frequency: "MONTHLY", + dataVizMissingData: 0, + Pretrial: null, + Sentenced: null, + "Transfer or Hold": 0.00024993751562109475, + }, + { + start_date: "Thu, 01 Dec 2022 00:00:00 GMT", + end_date: "Sun, 01 Jan 2023 00:00:00 GMT", + Pretrial: null, + frequency: "MONTHLY", + dataVizMissingData: 0, + Sentenced: null, + "Transfer or Hold": 0, + Unknown: null, + }, +]; + +beforeAll(() => { + jest.useFakeTimers("modern"); + jest.setSystemTime(new Date(2022, 7, 23)); +}); + +afterAll(() => { + jest.useRealTimers(); +}); + +describe("incrementMonth", () => { + test("incrementMonth increments month correctly", () => { + const testDate = new Date("Tue, 01 Mar 2022 00:00:00 GMT"); + const testDate2 = new Date("Thu, 01 Dec 2022 00:00:00 GMT"); + expect(incrementMonth(testDate).toUTCString()).toBe( + "Fri, 01 Apr 2022 00:00:00 GMT" + ); + expect(incrementMonth(testDate2).toUTCString()).toBe( + "Sun, 01 Jan 2023 00:00:00 GMT" + ); + }); +}); + +describe("incrementYear", () => { + test("incrementYear increments year correctly", () => { + const testDate = new Date("Tue, 01 Mar 2022 00:00:00 GMT"); + const testDate2 = new Date("Thu, 01 Dec 2022 00:00:00 GMT"); + expect(incrementYear(testDate).toUTCString()).toBe( + "Wed, 01 Mar 2023 00:00:00 GMT" + ); + expect(incrementYear(testDate2).toUTCString()).toBe( + "Fri, 01 Dec 2023 00:00:00 GMT" + ); + }); +}); + +describe("filterByTimeRange", () => { + test("filterByTimeRange filters through different months", () => { + expect(filterByTimeRange(testDatapoints, 6).length).toBe(10); + expect(filterByTimeRange(testDatapoints, 12).length).toBe(16); + }); +}); + +describe("transformToRelativePerchanges", () => { + test("transformToRelativePerchanges transforms datapoints correctly", () => { + expect(transformToRelativePerchanges(testDatapoints2)).toStrictEqual( + testDatapoints2Percentages + ); + }); +}); + +describe("filterNullDatapoints", () => { + test("filterNullDatapoints filters datapoints with all null dimensions only", () => { + expect(filterNullDatapoints(testDatapoints3)).toStrictEqual( + testDatapoints3WithoutNullDatapoints + ); + }); +}); + +describe("fillTimeGapsBetweenDatapoints", () => { + test("fillTimeGapsBetweenDatapoints adds datapoints between data", () => { + expect(fillTimeGapsBetweenDatapoints(testDatapoints4, 0)).toStrictEqual( + testDatapoints4WithGapDatapoints + ); + }); + test("fillTimeGapsBetweenDatapoints adds datapoints between data plus additional earlier month padding", () => { + expect(fillTimeGapsBetweenDatapoints(testDatapoints4, 120)).toStrictEqual( + testDatapoints4WithGapDatapoints2 + ); + }); +}); + +describe("transformData", () => { + test("putting it all together", () => { + expect(transformData(testDatapoints5, 60, "Percentage")).toStrictEqual( + testDatapoints5Transformed + ); + }); +}); diff --git a/publisher/src/components/DataViz/utils.ts b/publisher/src/components/DataViz/utils.ts new file mode 100644 index 000000000..45e0831f5 --- /dev/null +++ b/publisher/src/components/DataViz/utils.ts @@ -0,0 +1,335 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { mapValues, pickBy } from "lodash"; + +import { + Datapoint, + DatapointsViewSetting, + DataVizAggregateName, + DataVizTimeRange, +} from "../../shared/types"; +import { formatNumberInput } from "../../utils"; + +export const thirtyOneDaysInSeconds = 2678400000; +export const threeHundredSixtySixDaysInSeconds = 31622400000; + +export const nextMonthMap = new Map([ + ["Jan", "Feb"], + ["Feb", "Mar"], + ["Mar", "Apr"], + ["Apr", "May"], + ["May", "Jun"], + ["Jun", "Jul"], + ["Jul", "Aug"], + ["Aug", "Sep"], + ["Sep", "Oct"], + ["Oct", "Nov"], + ["Nov", "Dec"], + ["Dec", "Jan"], +]); + +const abbreviatedMonths = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +]; + +export const splitUtcString = (utcString: string) => { + // the utc string can be split like this: + // const [dayOfWeek, day, month, year, time, timezone] = splitUtcString(str); + return utcString.split(" "); +}; + +export const getDatapointDimensions = (datapoint: Datapoint) => + // gets the datapoint object minus the non-dimension keys "start_date", "end_date", "frequency", "dataVizMissingData" + pickBy( + datapoint, + (val, key) => + key !== "start_date" && + key !== "end_date" && + key !== "frequency" && + key !== "dataVizMissingData" + ); + +export const sortDatapointDimensions = (dimA: string, dimB: string) => { + // sort alphabetically, except put "Other" and "Unknown" at the end. + if (dimA === "Other" && dimB === "Unknown") { + return -1; + } + if (dimB === "Other" && dimA === "Unknown") { + return 1; + } + if (dimA === "Other" || dimA === "Unknown") { + return 1; + } + if (dimB === "Other" || dimB === "Unknown") { + return -1; + } + return dimA.localeCompare(dimB); +}; + +export const getSumOfDimensionValues = (datapoint: Datapoint) => { + let sumOfDimensions = 0; + const dimensions = getDatapointDimensions(datapoint); + Object.values(dimensions).forEach((value) => { + sumOfDimensions += value as number; + }); + return sumOfDimensions; +}; + +// write my own month incrementer since Date.setMonth doesn't keep the date the same... +export const incrementMonth = (date: Date) => { + const [, day, month, year, time, timezone] = splitUtcString( + date.toUTCString() + ); + return new Date( + `${day} ${nextMonthMap.get(month)} ${ + month === "Dec" ? Number(year) + 1 : year + } ${time} ${timezone}` + ); +}; + +export const incrementYear = (date: Date) => { + const clonedDate = new Date(date.getTime()); + clonedDate.setFullYear(clonedDate.getFullYear() + 1); + return clonedDate; +}; + +// returns a new Date set to the GMT time zone +// for comparing with Datapoint time strings which are also set to 00:00:00 GMT. +export const createGMTDate = ( + day: number, + monthIndex: number, + year: number +) => { + return new Date( + `${day} ${abbreviatedMonths[monthIndex]} ${year} 00:00:00 GMT` + ); +}; + +export const getHighestTotalValue = (data: Datapoint[]) => { + let highestValue = 0; + data.forEach((datapoint) => { + const sumOfDimensions = getSumOfDimensionValues(datapoint); + if (sumOfDimensions > highestValue) { + highestValue = sumOfDimensions; + } + }); + return highestValue; +}; + +// functions to transform and filter an array of Datapoints to display in a chart + +export const filterByTimeRange = ( + data: Datapoint[], + monthsAgo: DataVizTimeRange +) => { + if (monthsAgo === 0) { + return data; + } + const earliestDate = new Date(); + earliestDate.setMonth(earliestDate.getMonth() - monthsAgo); + earliestDate.setHours( + earliestDate.getHours() - earliestDate.getTimezoneOffset() / 60 + ); // account for timezone offset since datapoint dates are in UTC+0 time. + return data.filter((dp) => { + return new Date(dp.start_date) >= earliestDate; + }); +}; + +export const transformToRelativePerchanges = (data: Datapoint[]) => { + return data.map((datapoint) => { + const dimensions = getDatapointDimensions(datapoint); + const sumOfDimensions = getSumOfDimensionValues(datapoint); + const dimensionsPercentage = mapValues(dimensions, (val, key) => { + if (typeof val === "number" && val !== 0) { + return val / sumOfDimensions; + } + return val; + }); + return { + ...datapoint, + ...dimensionsPercentage, + }; + }); +}; + +export const filterNullDatapoints = (data: Datapoint[]) => { + return data.filter((datapoint) => { + const dimensions = getDatapointDimensions(datapoint); + let hasReportedValues = false; + Object.values(dimensions).every((dimValue) => { + if (dimValue !== null) { + hasReportedValues = true; + return false; + } + return true; + }); + return hasReportedValues; + }); +}; + +/** + * A gap datapoint represents a time range with no reported data + * and is formatted by setting all dimension values to 0 + * and setting the value of "dataVizMissingData" to ~1/3 the height of the bar on the chart. + * + * This method generates gap datapoints between datapoints up to a certain number of months ago. + */ +export const fillTimeGapsBetweenDatapoints = ( + data: Datapoint[], + monthsAgo: number +) => { + if (data.length === 0) { + return data; + } + + const isAnnual = data[0].frequency === "ANNUAL"; + const increment = isAnnual ? incrementYear : incrementMonth; + const defaultBarValue = getHighestTotalValue(data) / 3; + const dataWithGapDatapoints = [...data]; + // create the map of dimensions with zero values + const dimensionsMap = mapValues(getDatapointDimensions(data[0]), (_) => 0); + + // loop through all the datapoints + let totalOffset = 0; // whenever we insert a gap datapoint into `dataWithGapDatapoints`, increment the totalOffset + let lastDate = new Date(); + if (isAnnual) { + lastDate.setFullYear(lastDate.getFullYear() - monthsAgo / 12); + } else { + lastDate.setMonth(lastDate.getMonth() - monthsAgo); + } + lastDate = createGMTDate( + 1, + isAnnual ? 0 : lastDate.getMonth(), + lastDate.getFullYear() + ); + for (let i = 0; i < data.length; i += 1) { + const currentDate = new Date(data[i].start_date); + const timeInterval = + data[0].frequency === "MONTHLY" + ? thirtyOneDaysInSeconds + : threeHundredSixtySixDaysInSeconds; + // this while loop can insert multiple gap datapoints between datapoints + // so must increment this offset to maintain correct insert order + let offset = 0; + while (currentDate.getTime() - lastDate.getTime() > timeInterval) { + lastDate = increment(lastDate); + dataWithGapDatapoints.splice(i + offset + totalOffset, 0, { + start_date: lastDate.toUTCString(), + end_date: increment(lastDate).toUTCString(), + dataVizMissingData: defaultBarValue, + frequency: data[0].frequency, + ...dimensionsMap, + }); + offset += 1; + } + totalOffset += offset; + lastDate = currentDate; + } + + return dataWithGapDatapoints; +}; + +export const transformData = ( + d: Datapoint[], + monthsAgo: DataVizTimeRange, + datapointsViewSetting: DatapointsViewSetting +) => { + let transformedData = [...d]; + + if (transformedData.length === 0) { + return transformedData; + } + + // filter by time range + transformedData = filterByTimeRange(transformedData, monthsAgo); + + transformedData = filterNullDatapoints(transformedData); + + // format data into percentages for percentage view + if (datapointsViewSetting === "Percentage") { + transformedData = transformToRelativePerchanges(transformedData); + } + + return fillTimeGapsBetweenDatapoints(transformedData, monthsAgo); +}; + +// get insights from data + +export const getPercentChangeOverTime = (data: Datapoint[]) => { + if (data.length > 0) { + const start = data[0][DataVizAggregateName] as number | undefined; + const end = data[data.length - 1][DataVizAggregateName] as + | number + | undefined; + if (start !== undefined && end !== undefined) { + const formattedPercentChange = formatNumberInput( + Math.round(((end - start) / start) * 100).toString() + ); + if (formattedPercentChange) { + return `${formattedPercentChange}%`; + } + } + } + return "N/A"; +}; + +export const getAverageTotalValue = (data: Datapoint[], isAnnual: boolean) => { + if (data.length > 0) { + let totalValueFound = false; + const avgTotalValue = + data.reduce((res, dp) => { + if (dp[DataVizAggregateName] !== undefined) { + totalValueFound = true; + return res + (dp[DataVizAggregateName] as number); + } + return res; + }, 0) / data.length; + if (totalValueFound && avgTotalValue !== undefined) { + const formattedAvgTotalValue = formatNumberInput( + Math.round(avgTotalValue).toString() + ); + if (formattedAvgTotalValue !== undefined) { + return `${formattedAvgTotalValue}/${isAnnual ? "yr" : "mo"}`; + } + } + } + return "N/A"; +}; + +export const getLatestDateFormatted = ( + data: Datapoint[], + isAnnual: boolean +) => { + const mostRecentDate = data[data.length - 1]?.start_date; + if (mostRecentDate) { + const [, , month, year] = splitUtcString(mostRecentDate); + return `${!isAnnual ? `${month} ` : ""}${year}`; + } + return "N/A"; +}; diff --git a/publisher/src/components/Error/NoAuthConfigErrorPage.tsx b/publisher/src/components/Error/NoAuthConfigErrorPage.tsx new file mode 100644 index 000000000..3cd503240 --- /dev/null +++ b/publisher/src/components/Error/NoAuthConfigErrorPage.tsx @@ -0,0 +1,38 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import styled from "styled-components/macro"; + +const NoAuthConfigError = styled.div` + height: 100vh; + width: 100vw; + display: flex; + justify-content: center; + align-items: center; +`; + +const NoAuthConfigErrorPage = () => { + return ( + + No authentication configuration detected. Please refresh the page or try + again later. + + ); +}; + +export default NoAuthConfigErrorPage; diff --git a/publisher/src/components/Forms/BinaryRadioButton.tsx b/publisher/src/components/Forms/BinaryRadioButton.tsx new file mode 100644 index 000000000..d61ad243b --- /dev/null +++ b/publisher/src/components/Forms/BinaryRadioButton.tsx @@ -0,0 +1,143 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { InputHTMLAttributes } from "react"; +import styled from "styled-components/macro"; + +import { palette, typography } from "../GlobalStyles"; + +export const BinaryRadioGroupContainer = styled.div` + display: flex; + flex-direction: column; + position: relative; +`; + +export const BinaryRadioGroupWrapper = styled.div` + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; +`; + +export const BinaryRadioGroupQuestion = styled.div` + ${typography.sizeCSS.medium} + display: flex; + align-items: center; + margin-top: 22px; + color: ${palette.solid.darkgrey}; +`; + +export const RadioButtonWrapper = styled.div` + display: flex; + flex: 1 1 0; + margin: 15px 0 0 0; + + &:first-child { + margin: 15px 10px 0 0; + } +`; + +export const RadioButtonElement = styled.input<{ + disabled?: boolean; +}>` + width: 0; + position: fixed; + opacity: 0; + + &:focus + label { + border: ${({ disabled }) => + disabled ? "none" : `1px solid ${palette.highlight.grey9}`}; + } + + &:checked + label { + background-color: ${palette.solid.blue}; + border-color: ${palette.solid.blue}; + color: ${palette.solid.white}; + } + + &:checked + label:hover { + background-color: ${({ disabled }) => + disabled ? "none" : palette.solid.darkblue}; + } + + &:hover { + cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")}; + } +`; + +export const RadioButtonLabel = styled.label<{ + disabled?: boolean; +}>` + ${typography.sizeCSS.medium} + width: 100%; + height: 56px; + display: flex; + justify-content: center; + align-items: center; + background: ${palette.highlight.grey1}; + padding: 16px 24px; + border: 1px solid ${palette.highlight.grey3}; + border-radius: 2px; + transition: 0.2s ease; + + &:hover { + cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")}; + background-color: ${({ disabled }) => + disabled ? "none" : palette.highlight.grey2}; + } +`; + +export const BinaryRadioGroupClearButton = styled.div<{ + disabled?: boolean; +}>` + ${typography.sizeCSS.small} + margin-top: 8px; + color: ${palette.solid.blue}; + text-decoration: underline; + + &:hover { + cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")}; + } +`; + +interface RadioButtonProps extends InputHTMLAttributes { + label: string; + context?: string; + metricKey?: string; +} + +/** Single radio button in the style of a regular button */ +export const BinaryRadioButton: React.FC = ({ + label, + context, + metricKey, + disabled, + ...props +}): JSX.Element => { + return ( + + + + {label} + + + ); +}; diff --git a/publisher/src/components/Forms/Dropdown.tsx b/publisher/src/components/Forms/Dropdown.tsx new file mode 100644 index 000000000..5e9442e62 --- /dev/null +++ b/publisher/src/components/Forms/Dropdown.tsx @@ -0,0 +1,92 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { SelectHTMLAttributes } from "react"; +import styled from "styled-components/macro"; + +import { palette, typography } from "../GlobalStyles"; + +const DropdownContainer = styled.div` + position: relative; + width: 100%; + &:first-child { + margin-right: 10px; + } +`; + +const DropdownSelection = styled.select` + ${typography.sizeCSS.large} + height: 71px; + width: 100%; + margin: 10px 0; + background: ${palette.highlight.lightblue1}; + color: ${palette.solid.darkgrey}; + caret-color: ${palette.solid.blue}; + border: none; + border-bottom: 1px solid ${palette.solid.blue}; + + padding-left: 16px; + margin-top: 15px; + + appearance: none; + + &:hover { + cursor: pointer; + } +`; + +const DropdownArrowContainer = styled.div` + pointer-events: none; + width: 30px; + height: 30px; + border-radius: 15px; + background: ${palette.highlight.lightblue2}; + position: absolute; + top: 35px; + right: 16px; + display: flex; + align-items: center; + justify-content: center; +`; + +const DropdownArrow = styled.div` + width: 12px; + height: 12px; + border: none; + border-bottom: 4px solid ${palette.solid.blue}; + border-right: 4px solid ${palette.solid.blue}; + transform: rotate(45deg) translate(-1px, -1px); +`; + +interface DropdownProps extends SelectHTMLAttributes { + children: JSX.Element[]; +} + +export const Dropdown: React.FC = ({ + value, + onChange, + children, +}) => ( + + + {children} + + + + + +); diff --git a/publisher/src/components/Forms/Form.styles.tsx b/publisher/src/components/Forms/Form.styles.tsx new file mode 100644 index 000000000..427424dda --- /dev/null +++ b/publisher/src/components/Forms/Form.styles.tsx @@ -0,0 +1,282 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import styled from "styled-components/macro"; + +import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; +import { + DATA_ENTRY_WIDTH, + ONE_PANEL_MAX_WIDTH, + SINGLE_COLUMN_MAX_WIDTH, + TWO_PANEL_MAX_WIDTH, +} from "../Reports/ReportDataEntry.styles"; + +export const PageWrapper = styled.div` + height: 100%; + width: 100%; + display: flex; + justify-content: center; + padding-top: ${HEADER_BAR_HEIGHT}px; + position: absolute; + top: 0; + z-index: 0; + background: ${palette.solid.white}; +`; + +export const FormWrapper = styled.div` + flex: 0 1 ${DATA_ENTRY_WIDTH}px; + display: flex; + flex-direction: column; + margin: 32px 360px 50px 360px; + + @media only screen and (max-width: ${TWO_PANEL_MAX_WIDTH}px) { + margin: 32px 24px 50px 360px; + } + + @media only screen and (max-width: ${ONE_PANEL_MAX_WIDTH}px) { + margin: 32px 24px 50px 24px; + } +`; + +export const Form = styled.form` + display: block; + padding-bottom: 100px; +`; + +type TitleWrapperProps = { + underlined?: boolean; +}; + +export const TitleWrapper = styled.div` + width: 100%; + display: flex; + flex-direction: column; + border-bottom: ${({ underlined }) => + underlined ? `1px solid ${palette.solid.darkgrey}` : `none`}; +`; + +export const PreTitle = styled.div` + ${typography.sizeCSS.normal} +`; + +export const OnePanelBackLinkContainer = styled(PreTitle)` + display: none; + @media only screen and (max-width: ${ONE_PANEL_MAX_WIDTH}px) { + display: block; + top: 0; + width: 100%; + margin-right: -1px; + margin-left: -1px; + margin-bottom: 26px; + background-color: ${palette.solid.white}; + z-index: 1; + } +`; + +export const MetricsSectionTitle = styled.div` + ${typography.sizeCSS.normal} + margin-top: 6px; + margin-right: 17px; +`; + +export const Title = styled.h1<{ scrolled?: boolean; sticky?: boolean }>` + ${({ scrolled }) => + scrolled ? typography.sizeCSS.medium : typography.sizeCSS.title} + + ${({ scrolled }) => scrolled && `padding-top: 16px;`} + + margin-top: 4px; + padding-bottom: 14px; + border-bottom: 1px solid ${palette.highlight.grey9}; + transition: 0.3s ease; + + ${({ sticky }) => + sticky && + ` + position: sticky; + top: ${HEADER_BAR_HEIGHT}px; + background: ${palette.solid.white}; + z-index: 2; + margin-right: -1px; + margin-left: -1px; + `} +`; + +export const Metric = styled.div<{ notReporting?: boolean }>` + margin-top: -6.5em; + padding-top: 6.5em; + margin-bottom: ${({ notReporting }) => (notReporting ? `50px` : `194px`)}; +`; + +export const MetricSectionTitleWrapper = styled.div` + display: flex; + align-items: flex-end; +`; + +export const MetricSectionTitle = styled.div<{ notReporting?: boolean }>` + ${typography.sizeCSS.large} + margin-right: 17px; + margin-top: 32px; + color: ${({ notReporting }) => + notReporting ? palette.highlight.grey8 : palette.solid.darkgrey}; +`; + +export const MetricSystemTitle = styled(MetricSectionTitle)<{ + firstTitle?: boolean; +}>` + color: ${palette.highlight.grey8}; + border-top: ${({ firstTitle }) => + firstTitle ? `none` : `1px solid ${palette.highlight.grey8}`}; + padding-top: ${({ firstTitle }) => (firstTitle ? `none` : `30px`)}; + width: 100%; + + &:first-child { + border-top: none; + } +`; + +export const MetricSectionSubTitle = styled.div` + ${typography.sizeCSS.medium} + color: ${palette.highlight.grey8}; + margin-top: 8px; + margin-bottom: 16px; +`; + +export const DisaggregationTabsContainer = styled.div` + display: flex; + flex-direction: column; +`; + +export const TabsRow = styled.div` + width: 100%; + display: flex; + margin-bottom: 32px; + border-bottom: 1px solid ${palette.solid.darkgrey}; +`; + +export const TabItem = styled.div<{ active?: boolean }>` + ${typography.sizeCSS.normal} + display: flex; + margin-right: 32px; + transition: 0.2s ease; + color: ${({ active }) => + active ? palette.solid.blue : palette.highlight.grey7}; + padding-bottom: 7px; + border-bottom: 3px solid + ${({ active }) => (active ? palette.solid.blue : `transparent`)}; + + &:hover { + cursor: pointer; + color: ${palette.solid.blue}; + } +`; + +export const DisaggregationHasInputIndicator = styled.div<{ + active?: boolean; + hasInput?: boolean; + error?: boolean; +}>` + height: 16px; + width: 16px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + margin-left: 8px; + align-self: center; + border: 1px solid ${palette.highlight.grey4}; + ${({ active, hasInput, error }) => + !active && + (hasInput || error) && + `border: none; filter: grayscale(1) opacity(0.3);`} +`; + +export const TabDisplay = styled.div` + width: 100%; + display: flex; + flex-wrap: wrap; + justify-content: space-between; +`; + +export const DisaggregationInputWrapper = styled.div` + label { + width: 100%; + padding-right: 60px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + @media only screen and (max-width: ${SINGLE_COLUMN_MAX_WIDTH}px) { + width: 100%; + + label { + width: 100%; + } + } +`; + +export const Button = styled.button` + ${typography.sizeCSS.medium} + width: 314px; + height: 56px; + display: flex; + justify-content: center; + align-items: center; + background: ${palette.highlight.grey1}; + border: 1px solid ${palette.highlight.grey3}; + border-radius: 2px; + + &:hover { + cursor: pointer; + background: ${palette.highlight.grey2}; + } +`; + +export const GoBack = styled.a` + color: ${palette.solid.blue}; + transition: 0.2s ease; + + &:hover { + cursor: pointer; + opacity: 0.85; + } + + &::after { + content: "← Back"; + } +`; + +export const GoBackToReportsOverviewLink = styled(GoBack)` + &::after { + content: "← Back to Reports Overview"; + } +`; + +export const OpacityGradient = styled.div` + width: 100%; + height: 200px; + position: fixed; + bottom: 0; + left: 0; + background: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 1)); + pointer-events: none; + + @media only screen and (max-width: ${ONE_PANEL_MAX_WIDTH}px) { + display: none; + } +`; diff --git a/publisher/src/components/Forms/Forms.test.tsx b/publisher/src/components/Forms/Forms.test.tsx new file mode 100644 index 000000000..7b3844c10 --- /dev/null +++ b/publisher/src/components/Forms/Forms.test.tsx @@ -0,0 +1,106 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { render, screen } from "@testing-library/react"; +import React from "react"; + +import { TextInput } from "."; + +test("Optional input label without value expected to be default position, font-size and color", () => { + render( + + ); + + const label = screen.getByText(/Total Staff/i); + + expect(window.getComputedStyle(label).top).toBe("26px"); + expect(window.getComputedStyle(label).fontSize).toBe("1.5rem"); + expect(window.getComputedStyle(label).color).toBe("rgba(23, 28, 43, 0.5)"); + + expect.hasAssertions(); +}); + +test("Required input label with value expected to shrink position, font-size and change color", () => { + render( + + ); + + const label = screen.getByText(/Total Staff/i); + + expect(window.getComputedStyle(label).top).toBe("12px"); + expect(window.getComputedStyle(label).fontSize).toBe("0.75rem"); + expect(window.getComputedStyle(label).color).toBe("rgb(0, 115, 229)"); + + expect.hasAssertions(); +}); + +test("Error description appears in document", () => { + render( + + ); + + const errorDescription = screen.getByText(/Please enter valid number./i); + + expect(errorDescription).toBeInTheDocument(); + + expect.hasAssertions(); +}); + +test("Error state changes text input colors to red", () => { + render( + + ); + + const input = screen.getByLabelText("Total Staff"); + + expect(window.getComputedStyle(input).background).toBe( + "rgba(221, 18, 18, 0.05)" + ); + + expect.hasAssertions(); +}); diff --git a/publisher/src/components/Forms/NotReportedIcon.tsx b/publisher/src/components/Forms/NotReportedIcon.tsx new file mode 100644 index 000000000..a16284ef2 --- /dev/null +++ b/publisher/src/components/Forms/NotReportedIcon.tsx @@ -0,0 +1,126 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components/macro"; + +import notReportedIcon from "../assets/not-reported-icon.png"; +import { palette, typography } from "../GlobalStyles"; +import { TWO_PANEL_MAX_WIDTH } from "../Reports/ReportDataEntry.styles"; + +export const NotReportedIconWrapper = styled.div<{ + size?: number; +}>` + height: ${({ size }) => size || "23"}px; + width: ${({ size }) => size || "23"}px; + position: relative; +`; + +export const NotReportedIconImg = styled.img<{ + size?: number; + lighter?: boolean; + hasTooltip?: boolean; +}>` + width: ${({ size }) => size || "23"}px; + height: ${({ size }) => size || "23"}px; + + ${({ lighter }) => lighter && `opacity: 0.6;`}; + ${({ hasTooltip }) => + hasTooltip && + ` + &:hover { + cursor: pointer; + } + `}; +`; + +export const NotReportedIconTooltip = styled.div` + width: 267px; + position: absolute; + z-index: 2; + background: ${palette.solid.darkgrey}; + color: ${palette.solid.white}; + padding: 15px; + border-radius: 5px; + box-shadow: 0px 4px 10px ${palette.highlight.grey3}; + ${typography.sizeCSS.normal} + + @media only screen and (max-width: ${TWO_PANEL_MAX_WIDTH}px) { + width: 167px; + left: -38%; + } +`; + +export const NotReportedIconTooltipHoverArea = styled.div<{ + size?: number; +}>` + position: absolute; + top: 0px; + left: -1px; + width: 300px; + height: 200px; + padding-top: ${({ size }) => (size ? size + 5 : 27)}px; + padding-left: ${({ size }) => (size ? size + 5 : 27)}px; +`; + +export const MetricsViewLink = styled.span` + color: ${palette.solid.white}; + text-decoration: underline; + + &:hover { + cursor: pointer; + } +`; + +export const NotReportedIcon: React.FC<{ + size?: number; + lighter?: boolean; + noTooltip?: boolean; +}> = ({ size, lighter, noTooltip }) => { + const [tooltipIsVisible, setTooltipIsVisible] = useState(false); + const navigate = useNavigate(); + + const hideTooltip = () => setTooltipIsVisible(false); + const showTooltip = () => setTooltipIsVisible(true); + + return ( + + + {tooltipIsVisible && !noTooltip && ( + + + This has been disabled by an admin because the data is unavailable.{" "} + If you have the data for this, consider changing the configuration + in the{" "} + navigate("/metrics")}> + Metrics View + + . + + + )} + + ); +}; diff --git a/publisher/src/components/Forms/TabbedDisaggregations.tsx b/publisher/src/components/Forms/TabbedDisaggregations.tsx new file mode 100644 index 000000000..7e43f0a52 --- /dev/null +++ b/publisher/src/components/Forms/TabbedDisaggregations.tsx @@ -0,0 +1,271 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { useEffect, useState } from "react"; + +import { Metric as MetricType } from "../../shared/types"; +import { useStore } from "../../stores"; +import successIcon from "../assets/status-check-icon.png"; +import errorIcon from "../assets/status-error-icon.png"; +import { DisaggregationDimensionTextInput } from "../Reports/DataEntryFormComponents"; +import { + DisaggregationHasInputIndicator, + DisaggregationInputWrapper, + DisaggregationTabsContainer, + NotReportedIcon, + TabDisplay, + TabItem, + TabsRow, +} from "."; + +export const TabbedDisaggregations: React.FC<{ + metric: MetricType; + reportMetrics: MetricType[]; + reportID: number; + currentIndex: number; + disabled?: boolean; + updateFieldDescription: (title?: string, description?: string) => void; +}> = ({ + metric, + reportMetrics, + reportID, + currentIndex, + disabled, + updateFieldDescription, +}) => { + const [activeDisaggregation, setActiveDisaggregation] = useState<{ + [metricKey: string]: { + disaggregationKey: string; + disaggregationIndex: number; + hasValue?: boolean; + }; + }>({}); + const [disaggregationHasInput, setDisaggregationHasInput] = useState<{ + [disaggregationKey: string]: { + hasInput?: boolean; + }; + }>({}); + const { formStore } = useStore(); + + useEffect( + () => { + metric.disaggregations.forEach((disaggregation, index) => { + searchDimensionsForInput(disaggregation.key, index); + }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + const checkMetricForErrorsInUpdatedValues = ( + metricKey: string, + disaggregationKey: string + ) => { + let foundErrors = false; + + if ( + formStore.disaggregations?.[reportID]?.[metricKey]?.[disaggregationKey] + ) { + Object.values( + formStore.disaggregations?.[reportID]?.[metricKey]?.[disaggregationKey] + ).forEach((dimension) => { + if (dimension.error) { + foundErrors = true; + } + }); + } + + return foundErrors; + }; + + const searchDimensionsForInput = ( + disaggregationKey: string, + disaggregationIndex: number + ) => { + let inputFoundInUpdate = false; + let inputFoundFromLastSave = false; + + reportMetrics[currentIndex]?.disaggregations[ + disaggregationIndex + ]?.dimensions?.forEach((dimension) => { + const updatedDimensionValue = + formStore.disaggregations[reportID]?.[metric.key]?.[ + disaggregationKey + ]?.[dimension.key]?.value; + + if ( + dimension.value && + !inputFoundFromLastSave && + updatedDimensionValue !== "" + ) + inputFoundFromLastSave = true; + }); + + if ( + formStore.disaggregations[reportID]?.[metric.key]?.[disaggregationKey] + ) { + Object.values( + formStore.disaggregations[reportID]?.[metric.key]?.[disaggregationKey] + ).forEach((dimension) => { + if (dimension.value && !inputFoundInUpdate) inputFoundInUpdate = true; + }); + } + + if (inputFoundInUpdate || inputFoundFromLastSave) { + setDisaggregationHasInput((prev) => { + return { + ...prev, + [disaggregationKey]: { hasInput: true }, + }; + }); + } else { + setDisaggregationHasInput((prev) => { + return { + ...prev, + [disaggregationKey]: { hasInput: false }, + }; + }); + } + }; + + const updateActiveDisaggregationTab = ( + metricKey: string, + disaggregationKey: string, + disaggregationIndex: number + ) => { + searchDimensionsForInput(disaggregationKey, disaggregationIndex); + setActiveDisaggregation((prev) => ({ + ...prev, + [metricKey]: { + disaggregationKey, + disaggregationIndex, + }, + })); + }; + + const renderIcon = ( + foundError: boolean, + disaggregationEnabled: boolean | undefined, + hasInput: boolean | undefined + ) => { + if (!disaggregationEnabled) { + return ; + } + + if (foundError && disaggregationEnabled) { + return ; + } + + if (hasInput && !foundError && disaggregationEnabled) { + return ; + } + + return <>; + }; + + return ( + + + {metric.disaggregations.map((disaggregation, disaggregationIndex) => { + const foundError = checkMetricForErrorsInUpdatedValues( + metric.key, + disaggregation.key + ); + return ( + + updateActiveDisaggregationTab( + metric.key, + disaggregation.key, + disaggregationIndex + ) + } + > + {disaggregation.display_name} + + {renderIcon( + foundError, + disaggregation.enabled, + disaggregationHasInput[disaggregation.key]?.hasInput + )} + + + ); + })} + + + + {reportMetrics[currentIndex].disaggregations[ + activeDisaggregation[metric.key]?.disaggregationIndex || 0 + ]?.dimensions.map((dimension, dimensionIndex) => { + const activeDisaggregationOrZerothIndex = + activeDisaggregation[metric.key]?.disaggregationIndex || 0; + const disaggregation = + metric.disaggregations[activeDisaggregationOrZerothIndex]; + + return ( + + searchDimensionsForInput( + disaggregation.key, + activeDisaggregationOrZerothIndex + ) + } + > + + updateFieldDescription( + dimension.label, + dimension.reporting_note + ) + } + disabled={ + disabled || !disaggregation.enabled || !dimension.enabled + } + clearFieldDescription={() => updateFieldDescription(undefined)} + /> + + ); + })} + + + ); +}; diff --git a/publisher/src/components/Forms/TextInput.tsx b/publisher/src/components/Forms/TextInput.tsx new file mode 100644 index 000000000..0e1f35c93 --- /dev/null +++ b/publisher/src/components/Forms/TextInput.tsx @@ -0,0 +1,364 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { InputHTMLAttributes, useState } from "react"; +import styled from "styled-components/macro"; + +import { FormError } from "../../shared/types"; +import { rem } from "../../utils"; +import infoRedIcon from "../assets/info-red-icon.png"; +import statusCheckIcon from "../assets/status-check-icon.png"; +import statusErrorIcon from "../assets/status-error-icon.png"; +import { palette, typography } from "../GlobalStyles"; +import { NotReportedIcon } from "."; + +export const InputWrapper = styled.div` + position: relative; + display: flex; + flex-direction: column; + margin-bottom: 32px; +`; + +type InputProps = { + error?: string; + placeholder?: string; + multiline?: boolean; + persistLabel?: boolean; + notReporting?: boolean; +}; + +export const Input = styled.input` + ${typography.sizeCSS.large} + line-height: ${rem("30px")}; + resize: none; + height: ${({ multiline }) => (multiline ? "200px;" : "71px;")}; + padding: ${({ persistLabel }) => + persistLabel ? "42px 16px 16px 16px" : "16px 55px 10px 16px"}}; + background: ${({ value, error, notReporting }) => { + if (error) { + return palette.highlight.red; + } + if (notReporting) { + return palette.highlight.grey1; + } + return value || value === 0 + ? palette.highlight.lightblue1 + : palette.highlight.grey1; + }}; + ${({ notReporting }) => notReporting && `color: ${palette.highlight.grey6}`}; + + caret-color: ${({ error }) => { + if (error) { + return palette.solid.red; + } + return palette.solid.blue; + }}; + + border: none; + border-bottom: 1px solid + ${({ value, error, disabled }) => { + if (error) { + return palette.solid.red; + } + if (disabled) { + return palette.highlight.grey8; + } + return value || value === 0 + ? palette.solid.blue + : palette.highlight.grey9; + }}; + + &:hover { + border-bottom: ${({ disabled }) => + disabled ? undefined : `1px solid ${palette.solid.blue}`}; + } + + &:focus ~ label { + ${typography.sizeCSS.small} + ${({ persistLabel }) => !persistLabel && "display: none"}; + top: 12px; + color: ${({ error }) => { + if (error) { + return palette.solid.red; + } + return palette.solid.blue; + }}; + } + + &:hover ~ label { + color: ${({ error, disabled }) => + !error && !disabled && palette.solid.blue}; + } + + &::placeholder { + opacity: 0; + transition: 0.2s; + } + + &:focus::placeholder { + opacity: 1; + transition: 0.2s; + color: ${palette.highlight.grey6}; + } +`; + +type InputLabelProps = { + inputHasValue?: boolean; + isDisabled?: boolean; + error?: string; + persistLabel?: boolean; + notReporting?: boolean; +}; + +export const InputLabel = styled.label` + ${({ inputHasValue }) => + inputHasValue ? typography.sizeCSS.small : typography.sizeCSS.large} + + /* If persistLabel is false, the label is visible only when the input has no value + * If persistLabel is true, when the input has value, show the label above the value + */ + ${({ persistLabel, inputHasValue }) => + !persistLabel && inputHasValue && "display: none;"} + + min-height: 50px; + position: absolute; + top: ${({ inputHasValue }) => (inputHasValue ? "12px" : "26px")}; + left: 16px; + z-index: -1; + transition: 0.2s ease; + + color: ${({ error, isDisabled, inputHasValue, notReporting }) => { + if (error) { + return palette.solid.red; + } + if (isDisabled || notReporting) { + return palette.highlight.grey6; + } + return inputHasValue ? palette.solid.blue : palette.highlight.grey8; + }}; +`; + +type ErrorLabelProps = { + isDisabled?: boolean; + error?: string; + multiline?: boolean; +}; + +export const ErrorLabel = styled.span` + ${typography.sizeCSS.small} + ${({ error }) => error && `color: ${palette.solid.red};`}; + ${({ isDisabled }) => isDisabled && `color: ${palette.highlight.grey8};`} + margin-top: 8px; + position: absolute; + ${({ multiline }) => `top: ${multiline ? "200" : "71"}px;`}; + display: flex; + align-items: flex-start; + justify-content: flex-start; + width: 100%; +`; + +export const LabelChipPosition = styled.span` + position: absolute; + top: 24px; + right: 16px; +`; + +export const RequiredChip = styled.span` + ${typography.sizeCSS.small} + background-color: ${palette.solid.blue}; + justify-content: center; + align-items: center; + height: 24px; + padding: 4px 8px; + color: ${palette.solid.white}; + + &::after { + content: "Required"; + } +`; + +export const InputTooltip = styled.div` + position: absolute; + top: 72px; + border-radius: 4px; + padding: 16px; + background-color: ${palette.solid.darkgrey}; + color: ${palette.solid.white}; +`; + +export const ErrorIconContainer = styled.span` + transform: translate(1px, -1px); + flex-grow: 1; + z-index: 1; +`; + +export const ErrorInfo = styled.div` + position: absolute; + background-color: ${palette.solid.red}; + color: ${palette.solid.white}; + border-radius: 4px; + z-index: 1; + padding: 16px; + max-width: 300px; + bottom: 24px; +`; + +interface ErrorWithTooltipProps { + error: FormError; + disabled?: boolean; + multiline?: boolean; +} + +export const ErrorWithTooltip: React.FC = ({ + error, + disabled, + multiline, +}): JSX.Element => { + const [showErrorInfo, setShowErrorInfo] = useState(); + return ( + + {error.message} + {error?.info && ( + + setShowErrorInfo(true)} + onMouseLeave={() => setShowErrorInfo(false)} + /> + {showErrorInfo && {error.info}} + + )} + + ); +}; + +interface TextInputProps extends InputHTMLAttributes { + label: string; + error?: FormError; + valueLabel?: string; + multiline?: boolean; + persistLabel?: boolean; + metricKey?: string; + notReporting?: boolean; +} + +export const TextInput: React.FC = ({ + label, + error, + valueLabel, + multiline, + placeholder, + persistLabel, + metricKey, + notReporting, + ...props +}): JSX.Element => { + const [showTooltip, setShowTooltip] = useState(); + const { name, value, disabled } = props; + + const showTooltipIfTruncated = ( + e: React.MouseEvent + ) => { + const labelElement = e.currentTarget.querySelector("label") as HTMLElement; + if (labelElement.offsetWidth < labelElement.scrollWidth) { + setShowTooltip(true); + } + }; + const clearTooltip = () => setShowTooltip(false); + + return ( + + {/* Text Input */} + + + {/* Text Input Label (appears inside of text input) */} + + {label} + + + {showTooltip && {name}} + + {/* Error Description (appears below text input) */} + {error && ( + + )} + + {/* Label Chip (appears inside of text input on the right) */} + + {/* Chip: Required */} + {/* Disable the Required Chip for now. Refer to https://github.com/Recidiviz/recidiviz-data/pull/13849 for more information */} + {/* {required && !error && !value && ( + + + + )} */} + {/* Chip: Not Reporting Status */} + {notReporting && ( + + + + )} + + {/* Chip: Error Status */} + {error && !notReporting && ( + + + + )} + + {/* Chip: Validated Successfully Status */} + {!error && !notReporting && value && ( + + + + )} + + ); +}; diff --git a/publisher/src/components/Forms/index.ts b/publisher/src/components/Forms/index.ts new file mode 100644 index 000000000..23fbe9a3e --- /dev/null +++ b/publisher/src/components/Forms/index.ts @@ -0,0 +1,22 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./BinaryRadioButton"; +export * from "./Form.styles"; +export * from "./NotReportedIcon"; +export * from "./TabbedDisaggregations"; +export * from "./TextInput"; diff --git a/publisher/src/components/GlobalStyles/Palette.ts b/publisher/src/components/GlobalStyles/Palette.ts new file mode 100644 index 000000000..ba52dab94 --- /dev/null +++ b/publisher/src/components/GlobalStyles/Palette.ts @@ -0,0 +1,62 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export const palette = { + solid: { + red: `rgba(221, 18, 18, 1)`, + orange: `rgba(255, 128, 0, 1)`, + green: `rgba(0, 117, 64, 1)`, + lightgreen: `rgba(239, 244, 245, 1)`, + blue: `rgba(0, 115, 229, 1)`, + darkblue: `rgba(0, 86, 224, 1)`, + grey: `rgba(119, 119, 119, 1)`, + darkgrey: `rgba(23, 28, 43, 1)`, + white: `rgba(255,255,255, 1)`, + offwhite: `rgba(239, 244, 245, 1)`, + }, + highlight: { + red: `rgba(221, 18, 18, 0.05)`, + blue: `rgba(0, 115, 229, 0.1)`, + green: `rgba(0, 117, 65, 0.1)`, + lightblue1: `rgba(0, 115, 229, 0.05)`, + lightblue2: `rgba(0, 115, 229, 0.25)`, + grey1: `rgba(23, 28, 43, 0.05)`, + grey2: `rgba(23, 28, 43, 0.1)`, + grey3: `rgba(23, 28, 43, 0.15)`, + grey4: `rgba(23, 28, 43, 0.20)`, + grey5: `rgba(23, 28, 43, 0.25)`, + grey6: `rgba(23, 28, 43, 0.3)`, + grey7: `rgba(23, 28, 43, 0.4)`, + grey8: `rgba(23, 28, 43, 0.5)`, + grey9: `rgba(23, 28, 43, 0.6)`, + grey10: `rgba(23, 28, 43, 0.7)`, + grey11: `rgba(23, 28, 43, 0.8)`, + grey12: `rgba(23, 28, 43, 0.9)`, + }, + dataViz: { + bar1: "#133158", + bar2: "#5C8970", + bar3: "#D4A244", + bar4: "#6E4245", + bar5: "#BD484A", + bar6: "#353A6E", + bar7: "#BA845F", + bar8: "#64859E", + bar9: "#596041", + bar10: "#C36C3B", + }, +}; diff --git a/publisher/src/components/GlobalStyles/Typography.ts b/publisher/src/components/GlobalStyles/Typography.ts new file mode 100644 index 000000000..b027970b9 --- /dev/null +++ b/publisher/src/components/GlobalStyles/Typography.ts @@ -0,0 +1,55 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { rem } from "../../utils"; + +export const typography = { + family: "Inter", + letterSpacing: "-0.02em", + sizeCSS: { + headline: ` + font-size: ${rem("64px")}; + line-height: ${rem("64px")}; + font-weight: 500; + `, + title: ` + font-size: ${rem("32px")}; + line-height: ${rem("48px")}; + font-weight: 600; + `, + large: ` + font-size: ${rem("24px")}; + line-height: ${rem("24px")}; + font-weight: 500; + `, + medium: ` + font-size: ${rem("18px")}; + line-height: ${rem("24px")}; + font-weight: 500; + `, + normal: ` + font-size: ${rem("14px")}; + line-height: ${rem("22px")}; + font-weight: 500; + `, + small: ` + font-size: ${rem("12px")}; + line-height: ${rem("16px")}; + font-weight: 600; + `, + }, +}; diff --git a/publisher/src/components/GlobalStyles/constants.ts b/publisher/src/components/GlobalStyles/constants.ts new file mode 100644 index 000000000..eec628226 --- /dev/null +++ b/publisher/src/components/GlobalStyles/constants.ts @@ -0,0 +1,18 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export const HEADER_BAR_HEIGHT = 64; diff --git a/publisher/src/components/GlobalStyles/index.ts b/publisher/src/components/GlobalStyles/index.ts new file mode 100644 index 000000000..5b304b329 --- /dev/null +++ b/publisher/src/components/GlobalStyles/index.ts @@ -0,0 +1,20 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./constants"; +export * from "./Palette"; +export * from "./Typography"; diff --git a/publisher/src/components/Header/Header.styles.tsx b/publisher/src/components/Header/Header.styles.tsx new file mode 100644 index 000000000..1cb71ad09 --- /dev/null +++ b/publisher/src/components/Header/Header.styles.tsx @@ -0,0 +1,54 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import styled from "styled-components/macro"; + +import { HEADER_BAR_HEIGHT } from "../GlobalStyles"; +import { palette } from "../GlobalStyles/Palette"; + +export const HeaderBar = styled.header` + width: 100%; + height: ${HEADER_BAR_HEIGHT}px; + display: flex; + justify-content: space-between; + align-items: center; + position: fixed; + top: 0; + z-index: 3; + background: ${palette.solid.white}; + padding: 16px 0; +`; + +export const LogoContainer = styled.div` + height: ${HEADER_BAR_HEIGHT}px; + width: ${HEADER_BAR_HEIGHT}px; + display: flex; + justify-content: center; + align-items: center; + background: ${palette.solid.green}; + transition: 0.3s ease; + + &:hover { + cursor: pointer; + opacity: 0.9; + } +`; + +export const Logo = styled.img` + width: 48px; + height: 48px; +`; diff --git a/publisher/src/components/Header/Header.tsx b/publisher/src/components/Header/Header.tsx new file mode 100644 index 000000000..a9d2adc51 --- /dev/null +++ b/publisher/src/components/Header/Header.tsx @@ -0,0 +1,39 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import { useNavigate } from "react-router-dom"; + +import logo from "../assets/jc-logo-vector.png"; +import Menu from "../Menu"; +import { HeaderBar, Logo, LogoContainer } from "."; + +const Header = () => { + const navigate = useNavigate(); + + return ( + + navigate("/")}> + + + + + + ); +}; + +export default Header; diff --git a/publisher/src/components/Header/index.ts b/publisher/src/components/Header/index.ts new file mode 100644 index 000000000..0705f0ce1 --- /dev/null +++ b/publisher/src/components/Header/index.ts @@ -0,0 +1,19 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export { default } from "./Header"; +export * from "./Header.styles"; diff --git a/publisher/src/components/Loading/Loading.tsx b/publisher/src/components/Loading/Loading.tsx new file mode 100644 index 000000000..05cb7d487 --- /dev/null +++ b/publisher/src/components/Loading/Loading.tsx @@ -0,0 +1,60 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import styled, { keyframes } from "styled-components/macro"; + +import sprite from "../assets/loader-sprite-horizontal.svg"; + +const loaderWidth = 144; +const spriteFrames = 104; + +const LoadingWrapper = styled.div` + height: 100vh; + width: 100vw; + display: flex; + justify-content: center; + align-items: center; + position: absolute; + top: 0; + left: 0; +`; + +const loadingSpriteAnimation = keyframes` + to { + background-position: -${loaderWidth * spriteFrames}px 0px; + } +`; + +const Loader = styled.div` + height: ${loaderWidth}px; + width: ${loaderWidth}px; + background-image: url(${sprite}); + background-repeat: no-repeat; + background-size: ${loaderWidth * spriteFrames}px ${loaderWidth}px; + background-position: 0px 0px; + animation: ${loadingSpriteAnimation} 1.5s steps(${spriteFrames}) infinite + alternate; +`; + +export const Loading = () => { + return ( + + + + ); +}; diff --git a/publisher/src/components/Loading/MiniLoader.tsx b/publisher/src/components/Loading/MiniLoader.tsx new file mode 100644 index 000000000..30b94b8c0 --- /dev/null +++ b/publisher/src/components/Loading/MiniLoader.tsx @@ -0,0 +1,88 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= +import React from "react"; +import styled, { keyframes } from "styled-components/macro"; + +const dotLoopAnimation = keyframes` + 0% { + transform: rotate(0); + } + 100% { + transform: rotate(360deg); + } +`; + +export const SVGContainer = styled.div` + height: 100%; + margin-left: 5px; +`; + +export const AnimatedSVGGroup = styled.g` + transform-origin: 8px 8px; + animation: ${dotLoopAnimation} 0.8s infinite steps(8, start); +`; + +export const MiniLoader: React.FC = () => { + return ( + + + dots anim + + + + + + + + + + + + + + + ); +}; diff --git a/publisher/src/components/Loading/index.ts b/publisher/src/components/Loading/index.ts new file mode 100644 index 000000000..829b62965 --- /dev/null +++ b/publisher/src/components/Loading/index.ts @@ -0,0 +1,18 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./Loading"; diff --git a/publisher/src/components/Menu/Menu.styles.tsx b/publisher/src/components/Menu/Menu.styles.tsx new file mode 100644 index 000000000..61fa095e8 --- /dev/null +++ b/publisher/src/components/Menu/Menu.styles.tsx @@ -0,0 +1,140 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= +import { DropdownMenuItem, DropdownToggle } from "@recidiviz/design-system"; +import styled from "styled-components/macro"; + +import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; +import { ONE_PANEL_MAX_WIDTH } from "../Reports/ReportDataEntry.styles"; + +export const MenuContainer = styled.nav` + font-family: ${typography.family}; + ${typography.sizeCSS.normal} + display: flex; + align-items: center; + padding: 0 24px; + gap: 24px; +`; + +export const MenuItem = styled.div<{ + active?: boolean; + highlight?: boolean; + buttonPadding?: boolean; +}>` + height: ${HEADER_BAR_HEIGHT}px; + padding-top: ${({ buttonPadding }) => (buttonPadding ? `5px` : `14px`)}; + border-top: 6px solid + ${({ active }) => (active ? palette.solid.blue : "transparent")}; + transition: 0.2s ease; + color: ${({ highlight }) => + highlight ? palette.solid.red : palette.solid.darkgrey}; + + a, + a:visited { + color: ${palette.solid.darkgrey}; + text-decoration: none; + transition: 0.2s ease; + } + + &:hover, + a:hover { + cursor: pointer; + color: ${palette.solid.blue}; + } +`; + +export const WelcomeUser = styled.div` + color: ${palette.highlight.grey8}; + border-right: 1px solid black; + padding-right: 24px; + + @media only screen and (max-width: ${ONE_PANEL_MAX_WIDTH}px) { + display: none; + } +`; + +export const ExtendedDropdownToggle = styled(DropdownToggle)<{ + noMargin?: boolean; +}>` + font-family: ${typography.family}; + ${typography.sizeCSS.normal} + padding: 0; + min-height: unset; + line-height: 0; + margin-bottom: ${({ noMargin }) => (noMargin ? "0" : "22px")}; + color: ${palette.solid.darkgrey}; + + &[aria-expanded="true"] { + color: ${palette.solid.blue}; + } + + &:hover { + color: ${palette.solid.blue}; + } + + &:focus { + color: ${palette.solid.darkgrey}; + } +`; + +export const ExtendedDropdownMenuItem = styled(DropdownMenuItem)<{ + highlight?: boolean; + noPadding?: boolean; +}>` + min-width: 264px; + display: flex; + align-items: center; + font-family: ${typography.family}; + ${typography.sizeCSS.normal} + color: ${({ highlight }) => + highlight ? palette.solid.red : palette.solid.darkgrey}; + height: auto; + padding: 0; + gap: 8px; + + ${({ noPadding }) => + !noPadding && + ` + padding: 16px; + + &:first-child { + padding: 10px 16px 16px 16px; + } + + &:last-child { + padding: 16px 16px 10px 16px; + } + `} + + &:not(:last-child) { + border-bottom: 1px solid ${palette.solid.offwhite}; + } + + &:focus { + background-color: transparent; + color: ${({ highlight }) => + highlight ? palette.solid.red : palette.solid.darkgrey}; + } + + &:hover { + color: ${palette.solid.blue}; + background-color: transparent; + + svg path { + stroke: ${palette.solid.blue}; + } + } +`; diff --git a/publisher/src/components/Menu/Menu.tsx b/publisher/src/components/Menu/Menu.tsx new file mode 100644 index 000000000..92715cc94 --- /dev/null +++ b/publisher/src/components/Menu/Menu.tsx @@ -0,0 +1,169 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= +import { Dropdown, DropdownMenu } from "@recidiviz/design-system"; +import { observer } from "mobx-react-lite"; +import React, { useEffect, useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; + +import { Permission } from "../../shared/types"; +import { useStore } from "../../stores"; +import { Button } from "../DataUpload"; +import { + ExtendedDropdownMenuItem, + ExtendedDropdownToggle, + MenuContainer, + MenuItem, + WelcomeUser, +} from "."; + +enum MenuItems { + Reports = "REPORTS", + CreateReport = "CREATE REPORT", + LearnMore = "LEARN MORE", + Settings = "SETTINGS", + Agencies = "AGENCIES", + Metrics = "METRICS", +} + +const Menu = () => { + const [activeMenuItem, setActiveMenuItem] = useState( + MenuItems.Reports + ); + const { authStore, api, userStore } = useStore(); + const navigate = useNavigate(); + const location = useLocation(); + + const logout = async (): Promise => { + try { + const response = (await api.request({ + path: "/auth/logout", + method: "POST", + })) as Response; + + if (response.status === 200 && authStore) { + return authStore.logoutUser(); + } + + return Promise.reject( + new Error( + "Something went wrong with clearing auth session or authStore is not initialized." + ) + ); + } catch (error) { + if (error instanceof Error) return error.message; + return String(error); + } + }; + + useEffect(() => { + if (location.pathname === "/") { + setActiveMenuItem(MenuItems.Reports); + } else if (location.pathname === "/reports/create") { + setActiveMenuItem(MenuItems.CreateReport); + } else if (location.pathname === "/settings") { + setActiveMenuItem(MenuItems.Settings); + } else if (location.pathname === "/metrics") { + setActiveMenuItem(MenuItems.Metrics); + } else { + setActiveMenuItem(undefined); + } + }, [location]); + + return ( + + + {userStore.nameOrEmail && + userStore.currentAgency?.name && + `Welcome, ${userStore.nameOrEmail} at ${userStore.currentAgency.name}`} + + + {/* Metrics View */} + navigate("/metrics")} + active={activeMenuItem === MenuItems.Metrics} + > + Metrics + + + {/* Reports */} + navigate("/")} + active={activeMenuItem === MenuItems.Reports} + > + Reports + + + {/* Learn More */} + + + Learn More + + + + {/* Agencies Dropdown */} + {(userStore.permissions.includes(Permission.RECIDIVIZ_ADMIN) || + userStore.permissions.includes(Permission.SWITCH_AGENCIES)) && ( + + + + Agencies + + + {userStore.userAgencies?.map((agency) => { + return ( + { + userStore.setCurrentAgencyId(agency.id); + }} + highlight={userStore.currentAgency?.id === agency.id} + > + {agency.name} + + ); + })} + + + + )} + + {/* Settings */} + navigate("/settings")} + active={activeMenuItem === MenuItems.Settings} + > + Settings + + + + Log Out + + + + + + + ); +}; + +export default observer(Menu); diff --git a/publisher/src/components/Menu/index.ts b/publisher/src/components/Menu/index.ts new file mode 100644 index 000000000..cdc66154b --- /dev/null +++ b/publisher/src/components/Menu/index.ts @@ -0,0 +1,19 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export { default } from "./Menu"; +export * from "./Menu.styles"; diff --git a/publisher/src/components/MetricsView/MetricsView.styles.tsx b/publisher/src/components/MetricsView/MetricsView.styles.tsx new file mode 100644 index 000000000..dc4144466 --- /dev/null +++ b/publisher/src/components/MetricsView/MetricsView.styles.tsx @@ -0,0 +1,330 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import styled from "styled-components/macro"; + +import { BinaryRadioGroupWrapper } from "../Forms"; +import { palette, typography } from "../GlobalStyles"; + +export const MetricsViewContainer = styled.div` + width: 100%; + display: flex; + flex-direction: column; + align-items: flex-start; +`; + +export const MetricsViewControlPanel = styled.div` + height: calc(100% - 170px); + width: 100%; + display: flex; + justify-content: space-between; +`; + +export const PanelContainerLeft = styled.div` + width: 35%; + height: 100%; + overflow: scroll; + padding: 10px 15px 0 15px; + + @media only screen and (max-width: 1000px) { + margin-right: 50px; + } +`; + +export const PanelContainerRight = styled.div` + width: 65%; + height: 100%; + display: flex; + position: relative; + flex-direction: column; +`; + +type MetricBoxContainerProps = { + enabled?: boolean; + selected?: boolean; +}; + +export const MetricBoxContainer = styled.div` + display: flex; + flex-direction: column; + border: 1px solid + ${({ selected }) => + selected ? palette.solid.blue : palette.highlight.grey2}; + border-radius: 12px; + padding: 15px; + margin-bottom: 11px; + transition: 0.2s ease; + color: ${({ enabled }) => + enabled ? palette.solid.darkgrey : palette.highlight.grey7}; + ${({ selected }) => + selected && `box-shadow: 0px 4px 10px ${palette.highlight.blue};`} + + &:hover { + cursor: pointer; + ${({ selected }) => + !selected && `border: 1px solid ${palette.highlight.lightblue2}`}; + } +`; + +export const MetricBoxWrapper = styled.div` + display: block; +`; + +export const ActiveMetricSettingHeader = styled.div` + position: relative; + z-index: 1; + background: ${palette.solid.white}; + padding: 10px 15px 0 15px; +`; + +export const MetricNameBadgeToggleWrapper = styled.div` + display: flex; + justify-content: space-between; + margin-bottom: 8px; +`; + +export const MetricNameBadgeWrapper = styled.div` + display: flex; + align-items: center; +`; + +type MetricNameProps = { isTitle?: boolean }; + +export const MetricName = styled.div` + ${({ isTitle }) => + isTitle ? typography.sizeCSS.title : typography.sizeCSS.medium} +`; + +export const MetricDescription = styled.div` + ${typography.sizeCSS.normal} + color: ${palette.highlight.grey9}; + + @media only screen and (max-width: 1000px) { + ${typography.sizeCSS.small} + } +`; + +export const MetricDetailsDisplay = styled.div` + width: 100%; + overflow-y: scroll; + padding: 24px 15px 0 15px; +`; + +export const MetricOnOffWrapper = styled.div` + margin-bottom: 49px; +`; + +export const Header = styled.div` + ${typography.sizeCSS.medium}; + margin-bottom: 16px; +`; + +export const Subheader = styled.div` + ${typography.sizeCSS.normal}; + color: ${palette.highlight.grey9}; + margin-bottom: 9px; +`; + +export const RadioButtonGroupWrapper = styled(BinaryRadioGroupWrapper)` + display: flex; +`; + +export const MetricDisaggregations = styled.div<{ enabled?: boolean }>` + display: block; + position: relative; + + ${({ enabled }) => + !enabled && + ` + &::after { + content: ''; + position: absolute; + background: ${palette.solid.white}; + height: 100%; + width: 100%; + top: 0; + opacity: 0.5; + } + `} +`; + +export const Disaggregation = styled.div` + display: block; + margin-bottom: 15px; +`; + +export const DisaggregationHeader = styled.div` + display: flex; + justify-content: space-between; + padding: 17px 0; + align-items: center; + + border-bottom: 1px solid ${palette.highlight.grey9}; +`; + +export const DisaggregationName = styled.div<{ enabled?: boolean }>` + ${typography.sizeCSS.large}; + + color: ${({ enabled }) => + enabled ? palette.solid.darkgrey : palette.highlight.grey8}; +`; + +export const Dimension = styled.div<{ enabled?: boolean }>` + ${typography.sizeCSS.medium}; + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 0; + border-bottom: 1px dashed ${palette.highlight.grey9}; + position: relative; + + &:last-child { + border-bottom: none; + } + + ${({ enabled }) => + !enabled && + ` + &::after { + content: ''; + position: absolute; + background: ${palette.solid.white}; + height: 100%; + width: 100%; + top: 0; + opacity: 0.5; + } + `} +`; + +export const DimensionTitleWrapper = styled.div` + display: flex; + align-items: center; +`; + +export const DimensionTitle = styled.div<{ enabled?: boolean }>` + display: block; + color: ${({ enabled }) => + enabled ? palette.solid.darkgrey : palette.highlight.grey8}; +`; + +export const MetricConfigurationContainer = styled.div` + display: block; +`; + +export const MetricContextContainer = styled.div` + display: block; +`; + +export const MetricContextItem = styled.div` + margin-top: 33px; +`; + +export const Label = styled.div<{ noBottomMargin?: boolean }>` + ${typography.sizeCSS.medium}; + margin-bottom: ${({ noBottomMargin }) => (noBottomMargin ? 0 : `16px`)}; +`; + +export const ToggleSwitchWrapper = styled.div` + display: flex; + align-items: center; + padding: 8px 0; +`; + +export const ToggleSwitch = styled.label` + position: relative; + display: inline-block; + width: 38px; + height: 24px; +`; + +export const ToggleSwitchInput = styled.input` + opacity: 0; + width: 0; + height: 0; + + &:checked + span { + background-color: ${palette.solid.blue}; + } + + &:checked + span:before { + transform: translateX(14px); + } +`; + +export const Slider = styled.span` + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: ${palette.solid.grey}; + border-radius: 34px; + transition: 0.3s; + + &:before { + content: ""; + height: 14px; + width: 14px; + position: absolute; + left: 5px; + bottom: 5px; + background-color: ${palette.solid.white}; + border-radius: 50%; + transition: 0.3s; + } +`; + +export const ToggleSwitchLabel = styled.span<{ switchedOn?: boolean }>` + ${typography.sizeCSS.normal} + color: ${({ switchedOn }) => + switchedOn ? palette.solid.blue : palette.solid.grey}; + text-transform: uppercase; + margin-right: 11px; + position: relative; + + &::after { + content: "${({ switchedOn }) => (switchedOn ? "ON" : "OFF")}"; + position: absolute; + top: -11px; + left: -27px; + } +`; + +export const MultipleChoiceWrapper = styled.div` + display: flex; + flex-wrap: wrap; + justify-content: space-between; + + div { + &:nth-child(odd) { + margin: 15px 10px 0 0; + } + + width: 90%; + flex: 40%; + } +`; + +export const MetricSettingsDisplayError = styled.div` + width: 100%; + height: 100%; + display: flex; + justify-content: center; + margin-top: 50px; +`; diff --git a/publisher/src/components/MetricsView/MetricsView.tsx b/publisher/src/components/MetricsView/MetricsView.tsx new file mode 100644 index 000000000..731a3a01d --- /dev/null +++ b/publisher/src/components/MetricsView/MetricsView.tsx @@ -0,0 +1,959 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { debounce as _debounce } from "lodash"; +import { reaction, when } from "mobx"; +import { observer } from "mobx-react-lite"; +import React, { useEffect, useRef, useState } from "react"; + +import { AgencySystems, FormError, ReportFrequency } from "../../shared/types"; +import { useStore } from "../../stores"; +import { + isPositiveNumber, + removeCommaSpaceAndTrim, + removeSnakeCase, +} from "../../utils"; +import { Badge, BadgeColorMapping } from "../Badge"; +import DatapointsView from "../DataViz/DatapointsView"; +import { + BinaryRadioButton, + BinaryRadioGroupClearButton, + BinaryRadioGroupContainer, + BinaryRadioGroupQuestion, + NotReportedIcon, + TextInput, +} from "../Forms"; +import { Loading } from "../Loading"; +import { PageTitle, TabbedBar, TabbedItem, TabbedOptions } from "../Reports"; +import { showToast } from "../Toast"; +import { + ActiveMetricSettingHeader, + Dimension, + DimensionTitle, + DimensionTitleWrapper, + Disaggregation, + DisaggregationHeader, + DisaggregationName, + Header, + Label, + MetricBoxContainer, + MetricBoxWrapper, + MetricConfigurationContainer, + MetricContextContainer, + MetricContextItem, + MetricDescription, + MetricDetailsDisplay, + MetricDisaggregations, + MetricName, + MetricNameBadgeToggleWrapper, + MetricNameBadgeWrapper, + MetricOnOffWrapper, + MetricsViewContainer, + MetricsViewControlPanel, + MultipleChoiceWrapper, + PanelContainerLeft, + PanelContainerRight, + RadioButtonGroupWrapper, + Slider, + Subheader, + ToggleSwitch, + ToggleSwitchInput, + ToggleSwitchLabel, + ToggleSwitchWrapper, +} from "."; + +type MetricsViewMetric = { + key: string; + display_name: string; + description: string; + frequency: string; + enabled: boolean; + system: AgencySystems; + contexts: { + key: string; + display_name: string; + reporting_note: string; + required: boolean; + type: string; + value: string | null; + multiple_choice_options?: string[]; + }[]; + disaggregations: { + key: string; + display_name: string; + enabled: boolean; + dimensions: { + key: string; + label: string; + reporting_note: string; + enabled: boolean; + }[]; + }[]; +}; + +type MetricBoxProps = { + metricKey: string; + displayName: string; + frequency: ReportFrequency; + description: string; + enabled?: boolean; + activeMetricKey: string; + setActiveMetricKey: React.Dispatch>; +}; + +const reportFrequencyBadgeColors: BadgeColorMapping = { + ANNUAL: "ORANGE", + MONTHLY: "GREEN", +}; + +const MetricBox: React.FC = ({ + metricKey, + displayName, + frequency, + description, + enabled, + activeMetricKey, + setActiveMetricKey, +}): JSX.Element => { + return ( + setActiveMetricKey(metricKey)} + enabled={enabled} + selected={metricKey === activeMetricKey} + > + + + {displayName} + + {frequency} + + + + {!enabled && } + + + {description} + + ); +}; + +type MetricConfigurationProps = { + activeMetricKey: string; + metricSettings: { [key: string]: MetricsViewMetric }; + saveAndUpdateMetricSettings: ( + typeOfUpdate: "METRIC" | "DISAGGREGATION" | "DIMENSION" | "CONTEXT", + updatedSetting: MetricSettings, + debounce?: boolean + ) => void; +}; + +const MetricConfiguration: React.FC = ({ + activeMetricKey, + metricSettings, + saveAndUpdateMetricSettings, +}): JSX.Element => { + const metricDisplayName = metricSettings[activeMetricKey]?.display_name; + const metricEnabled = Boolean(metricSettings[activeMetricKey]?.enabled); + + return ( + + +
+ Are you currently able to report any part of this metric? +
+ + Answering “No” means that {metricDisplayName} will not appear on + automatically generated reports from here on out. You can change this + later. + + + + saveAndUpdateMetricSettings("METRIC", { + key: activeMetricKey, + enabled: true, + }) + } + /> + + saveAndUpdateMetricSettings("METRIC", { + key: activeMetricKey, + enabled: false, + }) + } + /> + +
+ + + {metricSettings[activeMetricKey]?.disaggregations.length > 0 && ( + <> +
Breakdowns
+ + Turning any of these breakdowns “Off” means that they will not + appear on automatically generated reports from here on out. You + can change this later. + + + )} + + {metricSettings[activeMetricKey]?.disaggregations?.map( + (disaggregation) => { + return ( + + + + {disaggregation.display_name} + + + + + + + saveAndUpdateMetricSettings("DISAGGREGATION", { + key: activeMetricKey, + disaggregations: [ + { + key: disaggregation.key, + enabled: !disaggregation.enabled, + }, + ], + }) + } + /> + + + + + + {disaggregation?.dimensions.map((dimension) => { + return ( + + + + {dimension.label} + + + + + + + { + if (disaggregation.enabled) { + saveAndUpdateMetricSettings("DIMENSION", { + key: activeMetricKey, + disaggregations: [ + { + key: disaggregation.key, + dimensions: [ + { + key: dimension.key, + enabled: !dimension.enabled, + }, + ], + }, + ], + }); + } + }} + /> + + + + + ); + })} + + ); + } + )} +
+
+ ); +}; + +type MetricSettingsUpdateOptions = + | "METRIC" + | "DISAGGREGATION" + | "DIMENSION" + | "CONTEXT"; + +type MetricContextConfigurationProps = { + metricKey: string; + contexts: { + key: string; + display_name: string; + reporting_note: string; + required: boolean; + type: string; + value: string | null; + multiple_choice_options?: string[]; + }[]; + saveAndUpdateMetricSettings: ( + typeOfUpdate: MetricSettingsUpdateOptions, + updatedSetting: MetricSettings, + debounce?: boolean + ) => void; +}; + +const MetricContextConfiguration: React.FC = ({ + metricKey, + contexts, + saveAndUpdateMetricSettings, +}) => { + const [contextErrors, setContextErrors] = useState<{ + [key: string]: FormError; + }>(); + + const contextNumberValidation = (key: string, value: string) => { + const cleanValue = removeCommaSpaceAndTrim(value); + + if (!isPositiveNumber(cleanValue) && cleanValue !== "") { + setContextErrors({ + [key]: { + message: "Please enter a valid number.", + }, + }); + + return false; + } + + setContextErrors((prev) => { + const otherContextErrors = { ...prev }; + delete otherContextErrors[key]; + + return otherContextErrors; + }); + return true; + }; + + useEffect(() => { + contexts.forEach((context) => { + if (context.type === "NUMBER") { + contextNumberValidation(context.key, context.value || ""); + } + }); + }, [contexts]); + + return ( + + + Anything entered here will appear as the default value for all reports. + If you are entering data for a particular month, you can still replace + this as necessary. + + + {contexts?.map((context) => ( + + {context.type === "BOOLEAN" && ( + <> + + + + saveAndUpdateMetricSettings("CONTEXT", { + key: metricKey, + contexts: [{ key: context.key, value: "yes" }], + }) + } + /> + + saveAndUpdateMetricSettings("CONTEXT", { + key: metricKey, + contexts: [{ key: context.key, value: "no" }], + }) + } + /> + + + saveAndUpdateMetricSettings("CONTEXT", { + key: metricKey, + contexts: [{ key: context.key, value: "" }], + }) + } + > + Clear Input + + + )} + + {(context.type === "TEXT" || context.type === "NUMBER") && ( + <> + + { + if (context.type === "NUMBER") { + contextNumberValidation(context.key, e.currentTarget.value); + } + + saveAndUpdateMetricSettings( + "CONTEXT", + { + key: metricKey, + contexts: [ + { key: context.key, value: e.currentTarget.value }, + ], + }, + true + ); + }} + /> + + )} + + {context.type === "MULTIPLE_CHOICE" && ( + + + {context.display_name} + + + + {context.multiple_choice_options?.map((option) => ( + + saveAndUpdateMetricSettings("CONTEXT", { + key: metricKey, + contexts: [{ key: context.key, value: option }], + }) + } + /> + ))} + + + saveAndUpdateMetricSettings("CONTEXT", { + key: metricKey, + contexts: [{ key: context.key, value: "" }], + }) + } + > + Clear Input + + + )} + + ))} + + ); +}; + +export type MetricSettings = { + key: string; + enabled?: boolean; + contexts?: { + key: string; + value: string; + }[]; + disaggregations?: { + key: string; + enabled?: boolean; + dimensions?: { + key: string; + enabled: boolean; + }[]; + }[]; +}; + +export const MetricsView: React.FC = observer(() => { + const { reportStore, userStore, datapointsStore } = useStore(); + const configPanelRef = useRef(null); + + // TODO(#13805) Temporarily hiding the data tab until it is implemented. Currently it's only visible to Recidiviz admins. + const configSections = ["Data", "Configuration", "Context"]; + type ConfigSections = typeof configSections[number]; + + const [activeMetricFilter, setActiveMetricFilter] = useState(); + + const [activeConfigSection, setActiveConfigSection] = + useState("Data"); + + const [isLoading, setIsLoading] = useState(true); + + const [loadingError, setLoadingError] = useState( + undefined + ); + + const [activeMetricKey, setActiveMetricKey] = useState(""); + + const [metricSettings, setMetricSettings] = useState<{ + [key: string]: MetricsViewMetric; + }>({}); + + const [filteredMetricSettings, setFilteredMetricSettings] = useState<{ + [key: string]: MetricsViewMetric; + }>({}); + + const updateMetricSettings = ( + typeOfUpdate: MetricSettingsUpdateOptions, + updatedSetting: MetricSettings + ) => { + setMetricSettings((prev) => { + const metricKey = updatedSetting.key; + + if (typeOfUpdate === "METRIC") { + return { + ...prev, + [updatedSetting.key]: { + ...prev[metricKey], + enabled: Boolean(updatedSetting.enabled), + }, + }; + } + + if (typeOfUpdate === "DISAGGREGATION") { + const updatedDisaggregations = prev[metricKey].disaggregations.map( + (disaggregation) => { + /** Quick Note: for now, all updates happen one at a time thus leaving + * one item in the disaggregations/dimensions/contexts arrays that will + * be updated at any one time. We can update this in the future to handle + * updating multiple settings at one time if necessary. + */ + if ( + disaggregation.key === updatedSetting.disaggregations?.[0].key + ) { + /** + * When disaggregation is switched off, all dimensions are disabled. + * When disaggregation is switched on, all dimensions are enabled. + */ + if (!updatedSetting.disaggregations?.[0].enabled) { + return { + ...disaggregation, + enabled: false, + dimensions: disaggregation.dimensions.map((dimension) => { + return { + ...dimension, + enabled: false, + }; + }), + }; + } + return { + ...disaggregation, + enabled: Boolean(updatedSetting.disaggregations?.[0].enabled), + dimensions: disaggregation.dimensions.map((dimension) => { + return { + ...dimension, + enabled: true, + }; + }), + }; + } + return disaggregation; + } + ); + + return { + ...prev, + [updatedSetting.key]: { + ...prev[metricKey], + disaggregations: updatedDisaggregations, + }, + }; + } + + if (typeOfUpdate === "DIMENSION") { + const updatedDisaggregations = prev[metricKey].disaggregations.map( + (disaggregation) => { + if ( + disaggregation.key === updatedSetting.disaggregations?.[0].key + ) { + const lastDimensionDisabled = + disaggregation.dimensions.filter( + (dimension) => dimension.enabled + )?.length === 1; + + /** Disable disaggregation when last dimension toggle is switched off */ + if ( + !updatedSetting.disaggregations?.[0].dimensions?.[0].enabled && + lastDimensionDisabled + ) { + return { + ...disaggregation, + enabled: false, + dimensions: disaggregation.dimensions.map((dimension) => { + if ( + dimension.key === + updatedSetting.disaggregations?.[0].dimensions?.[0].key + ) { + return { + ...dimension, + enabled: Boolean( + updatedSetting.disaggregations?.[0].dimensions?.[0] + .enabled + ), + }; + } + return dimension; + }), + }; + } + + return { + ...disaggregation, + dimensions: disaggregation.dimensions.map((dimension) => { + if ( + dimension.key === + updatedSetting.disaggregations?.[0].dimensions?.[0].key + ) { + return { + ...dimension, + enabled: Boolean( + updatedSetting.disaggregations?.[0].dimensions?.[0] + .enabled + ), + }; + } + return dimension; + }), + }; + } + return disaggregation; + } + ); + + return { + ...prev, + [updatedSetting.key]: { + ...prev[metricKey], + disaggregations: updatedDisaggregations, + }, + }; + } + + if (typeOfUpdate === "CONTEXT") { + const updatedContext = prev[metricKey].contexts.map((context) => { + if (context.key === updatedSetting.contexts?.[0].key) { + return { + ...context, + value: updatedSetting.contexts?.[0].value, + }; + } + return context; + }); + + return { + ...prev, + [updatedSetting.key]: { + ...prev[metricKey], + contexts: updatedContext, + }, + }; + } + + return prev; + }); + }; + + const saveMetricSettings = async (updatedSetting: MetricSettings) => { + const response = (await reportStore.updateReportSettings([ + updatedSetting, + ])) as Response; + + if (response.status === 200) { + showToast(`Settings saved.`, true, "grey", 4000); + } else { + showToast(`Failed to save.`, true, "red", 4000); + } + }; + + const debouncedSave = useRef(_debounce(saveMetricSettings, 1500)).current; + + const saveAndUpdateMetricSettings = ( + typeOfUpdate: MetricSettingsUpdateOptions, + updatedSetting: MetricSettings, + debounce?: boolean + ) => { + updateMetricSettings(typeOfUpdate, updatedSetting); + if (debounce) { + debouncedSave(updatedSetting); + } else { + saveMetricSettings(updatedSetting); + } + }; + + const fetchAndSetReportSettings = async () => { + const response = (await reportStore.getReportSettings()) as + | Response + | Error; + + setIsLoading(false); + + if (response instanceof Error) { + return setLoadingError(response.message); + } + + const reportSettings = (await response.json()) as MetricsViewMetric[]; + const metricKeyToMetricMap: { [key: string]: MetricsViewMetric } = {}; + + reportSettings?.forEach((metric) => { + metricKeyToMetricMap[metric.key] = metric; + }); + + setMetricSettings(metricKeyToMetricMap); + setActiveMetricKey(Object.keys(metricKeyToMetricMap)[0]); + }; + + useEffect( + () => + // return when's disposer so it is cleaned up if it never runs + when( + () => userStore.userInfoLoaded, + async () => { + fetchAndSetReportSettings(); + + datapointsStore.getDatapoints(); + setActiveMetricFilter( + removeSnakeCase(userStore.currentAgency?.systems[0] as string) + ); + } + ), + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + // reload report overviews when the current agency ID changes + useEffect( + () => + // return disposer so it is cleaned up if it never runs + reaction( + () => userStore.currentAgencyId, + async (currentAgencyId, previousAgencyId) => { + // prevents us from calling getReportOverviews twice on initial load + if (previousAgencyId !== undefined) { + setIsLoading(true); + fetchAndSetReportSettings(); + datapointsStore.resetState(); + await datapointsStore.getDatapoints(); + setActiveMetricFilter( + removeSnakeCase(userStore.currentAgency?.systems[0] as string) + ); + } + } + ), + // eslint-disable-next-line react-hooks/exhaustive-deps + [userStore] + ); + + useEffect(() => { + const filteredMetricKeyToMetricMap: { [key: string]: MetricsViewMetric } = + {}; + + Object.values(metricSettings) + .filter( + (metric) => + metric.system.toLowerCase() === activeMetricFilter?.toLowerCase() + ) + ?.forEach((metric) => { + filteredMetricKeyToMetricMap[metric.key] = metric; + }); + + return setFilteredMetricSettings(filteredMetricKeyToMetricMap); + }, [metricSettings, activeMetricFilter]); + + if (isLoading) { + return ; + } + + if (!metricSettings[activeMetricKey]) { + return
Error: {loadingError}
; + } + + return ( + <> + + Metrics + + + + {userStore.currentAgency?.systems.map((filterOption) => ( + + setActiveMetricFilter(removeSnakeCase(filterOption)) + } + capitalize + > + {removeSnakeCase(filterOption.toLowerCase())} + + ))} + + + + + {/* List Of Metrics */} + + {filteredMetricSettings && + Object.values(filteredMetricSettings).map((metric) => ( + { + if (configPanelRef.current) { + configPanelRef.current.scrollTo({ + top: 0, + behavior: "smooth", + }); + } + }} + > + + + ))} + + + {/* Data | Configuration | Context */} + + + + + {metricSettings[activeMetricKey]?.display_name} + + + {metricSettings[activeMetricKey]?.frequency} + + + + + + {configSections.map((section) => ( + { + setActiveConfigSection(section); + if (configPanelRef.current) { + configPanelRef.current.scrollTo({ + top: 0, + behavior: "smooth", + }); + } + }} + > + {section} + + ))} + + + + + {/* Data */} + {activeConfigSection === "Data" && ( + + )} + + {/* Configuration */} + {activeConfigSection === "Configuration" && ( + + + + )} + + {/* Context */} + {activeConfigSection === "Context" && ( + + + + )} + + + + + ); +}); diff --git a/publisher/src/components/MetricsView/MetricsViewMocks.ts b/publisher/src/components/MetricsView/MetricsViewMocks.ts new file mode 100644 index 000000000..02dfe368a --- /dev/null +++ b/publisher/src/components/MetricsView/MetricsViewMocks.ts @@ -0,0 +1,163 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export const metricsViewMockResponse = [ + { + key: "LAW_ENFORCEMENT_BUDGET__metric/law_enforcement/budget/type", + display_name: "Annual Budget", + description: "Measures the total annual budget (in dollars) of the agency.", + frequency: "ANNUAL", + enabled: true, + contexts: [ + { + key: "PRIMARY_FUNDING_SOURCE", + display_name: "Binary question?", + reporting_note: "put your primary funding source here", + required: false, + type: "BOOLEAN", + value: "government funding", + }, + { + key: "PRIMARY_FUNDING_SOURCE2", + display_name: "Multiple choice", + reporting_note: "put your primary funding source here", + required: false, + type: "MULTIPLE_CHOICE", + multiple_choice_options: [ + "Choice 1", + "Choice 2", + "Choice 3", + "Choice 4", + ], + value: "government funding", + }, + { + key: "ADDITIONAL_CONTEXT", + display_name: "Additional context", + reporting_note: "Any additional context you want to provide", + required: false, + type: "TEXT", + value: "we are special, here's why", + }, + ], + disaggregations: [ + { + key: "law_enforcement/staff/type", + display_name: "Staff Types", + enabled: false, + dimensions: [ + { + key: "SUPPORT", + label: "Support", + reporting_note: "Staff: Support", + enabled: false, + }, + { + key: "SECURITY", + label: "Security", + reporting_note: "Staff: Security", + enabled: true, + }, + ], + }, + ], + }, + { + key: "READMISSION_RATE", + display_name: "Readmission Rate", + description: + "Measure the number of individuals admitted who had at least one other prison admission within the prior year.", + frequency: "MONTHLY", + enabled: false, + contexts: [ + { + key: "DEFINITION_OF_READMISSION", + display_name: "Definition of Readmission", + reporting_note: "Agency's definition of readmission.", + required: false, + type: "NUMBER", + value: null, + }, + { + key: "ADDITIONAL_CONTEXT", + display_name: "Additional Context", + reporting_note: + "Add any additional context that you would like to provide here.", + required: false, + type: "TEXT", + value: null, + }, + ], + disaggregations: [ + { + key: "READMISSION_KEY_TYPE", + display_name: "Readmission Types", + enabled: true, + dimensions: [ + { + key: "NEW_OFFENSE", + label: "New Offense", + reporting_note: "Readmission: New Offense", + enabled: true, + }, + { + key: "VIOLATION_OF_CONDITIONS", + label: "Violation of Conditions", + reporting_note: "Readmission: Violation of Conditions", + enabled: true, + }, + { + key: "OTHER", + label: "Other", + reporting_note: "Readmission: Other", + enabled: true, + }, + { + key: "UNKNOWN", + label: "Unknown", + reporting_note: "Readmission: Unknown", + enabled: true, + }, + { + key: "NEW_OFFENSE2", + label: "New Offense", + reporting_note: "Readmission: New Offense", + enabled: true, + }, + { + key: "VIOLATION_OF_CONDITIONS2", + label: "Violation of Conditions", + reporting_note: "Readmission: Violation of Conditions", + enabled: true, + }, + { + key: "OTHER2", + label: "Other", + reporting_note: "Readmission: Other", + enabled: true, + }, + { + key: "UNKNOWN2", + label: "Unknown", + reporting_note: "Readmission: Unknown", + enabled: true, + }, + ], + }, + ], + }, +]; diff --git a/publisher/src/components/MetricsView/index.ts b/publisher/src/components/MetricsView/index.ts new file mode 100644 index 000000000..1367ec56e --- /dev/null +++ b/publisher/src/components/MetricsView/index.ts @@ -0,0 +1,19 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./MetricsView"; +export * from "./MetricsView.styles"; diff --git a/publisher/src/components/Modal/Modal.tsx b/publisher/src/components/Modal/Modal.tsx new file mode 100644 index 000000000..6cbda3820 --- /dev/null +++ b/publisher/src/components/Modal/Modal.tsx @@ -0,0 +1,148 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { useEffect, useState } from "react"; +import styled, { css, keyframes } from "styled-components/macro"; + +import { HEADER_BAR_HEIGHT, palette } from "../GlobalStyles"; + +const ModalContainer = styled.div` + width: 100vw; + height: 100vh; + position: absolute; + top: 0; + left: 0; + z-index: 10; + background: ${palette.highlight.grey4}; +`; + +const fromBottomToTop = keyframes` + 0% { transform: translateY(100%); } + 100% { transform: translateY(0); } +`; + +const fromTopToBottom = keyframes` + 0% { transform: translateY(0); } + 100% { transform: translateY(100%); } +`; + +const animateBottomToTop = css` + animation: ${fromBottomToTop} 0.5s forwards ease; +`; + +const animateTopToBottom = css` + animation: ${fromTopToBottom} 0.3s forwards ease-in-out; +`; + +const MODAL_TOP_GAP = `${HEADER_BAR_HEIGHT}px`; + +const ModalElement = styled.div<{ showUnmountAnimation?: boolean }>` + height: calc(100% - ${MODAL_TOP_GAP}); + margin-top: ${MODAL_TOP_GAP}; + background: ${palette.solid.white}; + position: relative; + border-top: 1px solid ${palette.solid.darkgrey}; + ${({ showUnmountAnimation }) => + showUnmountAnimation ? animateTopToBottom : animateBottomToTop} +`; + +const CloseButton = styled.div` + width: 64px; + height: 64px; + display: flex; + align-items: center; + justify-content: center; + background: ${palette.solid.red}; + border-radius: 0.5px; + position: absolute; + top: 0; + right: 0; + z-index: 4; + + &:before { + content: ""; + position: absolute; + width: 32px; + height: 2px; + background: ${palette.solid.white}; + transform: rotate(135deg); + } + + &:after { + content: ""; + position: absolute; + width: 32px; + height: 2px; + background: ${palette.solid.white}; + transform: rotate(45deg); + } + + &:hover { + cursor: pointer; + opacity: 0.9; + } +`; + +type ModalProps = { + isOpen: boolean; + handleClose: () => void; +}; + +export const Modal: React.FC = ({ + isOpen, + handleClose, + children, +}) => { + const [showUnmountAnimation, setShowUnmountAnimation] = useState(false); + + const closeModal = () => { + if (showUnmountAnimation) { + setShowUnmountAnimation(false); + handleClose(); + } + }; + + const prepareModalToClose = (e: React.MouseEvent) => { + if (e.target !== e.currentTarget) return; + setShowUnmountAnimation(true); + }; + + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "unset"; + } + + return () => { + document.body.style.overflow = "unset"; + }; + }, [isOpen]); + + if (!isOpen) return null; + + return ( + + + {children} + + + ); +}; diff --git a/publisher/src/components/Modal/index.ts b/publisher/src/components/Modal/index.ts new file mode 100644 index 000000000..b9f7ed337 --- /dev/null +++ b/publisher/src/components/Modal/index.ts @@ -0,0 +1,18 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./Modal"; diff --git a/publisher/src/components/Onboarding/Onboarding.tsx b/publisher/src/components/Onboarding/Onboarding.tsx new file mode 100644 index 000000000..65579c4b6 --- /dev/null +++ b/publisher/src/components/Onboarding/Onboarding.tsx @@ -0,0 +1,717 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { useEffect, useRef, useState } from "react"; +import styled, { keyframes } from "styled-components/macro"; + +import { useStore } from "../../stores"; +import logo from "../assets/jc-logo-vector-onboarding.png"; +import { palette, typography } from "../GlobalStyles"; +import { + DATA_ENTRY_WIDTH, + ONE_PANEL_MAX_WIDTH, + SIDE_PANEL_WIDTH, + TWO_PANEL_MAX_WIDTH, +} from "../Reports/ReportDataEntry.styles"; +import { showToast } from "../Toast"; + +export const OnboardingContainer = styled.div` + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + position: fixed; + top: 0; + left: 0; + z-index: 99; + color: ${palette.solid.white}; +`; + +export const OnboardingBackdropContainer = styled.div` + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: ${palette.highlight.grey12}; + backdrop-filter: blur(3px); +`; + +const OnboardingIntroModal = styled.div` + width: 825px; + height: 599px; + display: flex; + flex-direction: column; + position: relative; + border-radius: 15.5px; + padding: 59px 48px 59px 59px; + background: linear-gradient(62.14deg, #007541 50.01%, #029553 100%); + z-index: 1; +`; + +const LogoImage = styled.img` + width: 441px; + position: absolute; + top: 0; + right: 0; + z-index: -1; +`; + +const OnboardingTitle = styled.div` + ${typography.sizeCSS.large} + line-height: 58px; + text-transform: uppercase; + margin-bottom: 25px; + + span { + display: block; + font-weight: 900; + font-size: 60px; + line-height: 34px; + } +`; + +const OnboardingIntroContent = styled.div` + ${typography.sizeCSS.large} + color: rgba(223, 246, 236, 1); + line-height: 36px; + margin-top: 14px; + + p:not(:last-child) { + margin-bottom: 34px; + } + + span { + font-weight: 700; + } +`; + +const OnboardingIntroGetStartedButton = styled.button` + ${typography.sizeCSS.title} + width: 100%; + height: 96px; + position: absolute; + bottom: 0; + left: 0; + border: none; + border-radius: 0 0 15.5px 15.5px; + background: rgba(2, 95, 53, 1); + color: ${palette.solid.white}; + + &:hover { + cursor: pointer; + color: rgba(223, 246, 236, 1); + } +`; + +const float = keyframes` + 0% { transform: translate(0, 0px); } + 50% { transform: translate(0, 10px); } + 100% { transform: translate(0, 0px); } +`; + +type OnboardModalPositionProps = + | "bottomleft" + | "bottomright" + | "reportsummary" + | "publishdata"; + +const OnboardingModal = styled.div<{ + position: OnboardModalPositionProps; + modalHeight: number; + topic: OnboardingTopics; + lastSection?: boolean; +}>` + width: 532px; + height: fit-content; + position: absolute; + z-index: 1; + top: 0; + left: 0; + bottom: 0; + right: 0; + transition: top 0.5s ease, left 0.5s ease, bottom 0.5s ease, right 0.5s ease, + background 1s ease; + ${({ position, modalHeight, topic }) => { + if (position === "bottomleft") { + return ` + top: calc(100% - ${modalHeight}px - 24px); + right: calc(100% - 532px - 24px); + bottom: 24px; + left: 24px; + `; + } + if (position === "bottomright") { + return ` + ${ + topic === "reportsview" + ? `top: unset;` + : `top: calc(100% - ${modalHeight}px - 24px);` + } + left: calc(100% - 532px - 24px); + bottom: 24px; + right: 24px; + `; + } + if (position === "reportsummary") { + return ` + bottom: calc(100% - ${modalHeight}px - 99px); + right: calc(100% - 532px - ${SIDE_PANEL_WIDTH}px); + top: 99px; + left: ${SIDE_PANEL_WIDTH}px; + `; + } + if (position === "publishdata") { + return ` + bottom: calc(100% - ${modalHeight}px - 113px); + left: calc(100% - 532px - ${SIDE_PANEL_WIDTH}px); + top: 113px; + right: ${SIDE_PANEL_WIDTH}px; + `; + } + }} + border-radius: 10px; + padding: 38px 31px 97px 38px; + background: ${({ lastSection }) => + lastSection ? palette.solid.green : palette.solid.blue}; + box-shadow: 0px 4px 10px rgba(53, 83, 98, 0.4); + animation: ${float} 3s infinite ease-in-out; + + @media only screen and (max-width: ${TWO_PANEL_MAX_WIDTH}px) { + ${({ position, modalHeight }) => { + if (position === "publishdata") { + return ` + top: calc(100% - ${modalHeight}px - 28px); + right: calc(100% - 532px - ${SIDE_PANEL_WIDTH}px); + left: ${SIDE_PANEL_WIDTH}px; + `; + } + return ``; + }} + } +`; + +const OnboardingModalTitle = styled.div` + ${typography.sizeCSS.large} + font-weight: 700; +`; + +const OnboardingModalContent = styled.div` + ${typography.sizeCSS.medium} + line-height: 27px; + margin-top: 11px; + + a { + color: ${palette.solid.white}; + } +`; + +const OnboardingModalActionButtonWrapper = styled.div<{ + lastSection?: boolean; +}>` + ${typography.sizeCSS.large} + height: 62px; + width: 100%; + display: flex; + align-items: center; + justify-content: flex-end; + padding-right: 31px; + position: absolute; + left: 0; + bottom: 0; + border-radius: 0px 0px 10px 10px; + background: ${({ lastSection }) => + lastSection + ? `linear-gradient(90deg, rgba(5, 101, 57, 0) 0%, #005931 100%)` + : `linear-gradient(90deg, rgba(0, 69, 137, 0) 0%, #004589 100%)`}; +`; + +const OnboardingProgressIndicatorWrapper = styled.div` + margin-right: 17px; + display: flex; +`; + +const OnboardingProgressIndicator = styled.div<{ filled?: boolean }>` + width: 10px; + height: 10px; + border-radius: 50%; + background: ${({ filled }) => + filled ? palette.solid.white : `rgba(255, 255, 255, 0.3)`}; + margin: 9px; + + &:hover { + cursor: pointer; + } +`; + +type OnboardingActionButtonProps = "Next" | "Finish" | "Close"; + +const OnboardingActionButton = styled.button<{ + action: OnboardingActionButtonProps; +}>` + ${typography.sizeCSS.large} + background: none; + border: none; + color: ${palette.solid.white}; + + &::after { + content: ${({ action }) => { + if (action === "Next") { + return `"Next →"`; + } + if (action === "Finish") { + return `"Finish →"`; + } + if (action === "Close") { + return `"Close"`; + } + }}; + } + + &:hover { + cursor: pointer; + opacity: 0.9; + } +`; + +type OnboardingFadedContainerProps = { + position: string; + currentSectionPosition?: string; +}; + +const OnboardingFadedContainer = styled.div` + height: 100%; + background: ${palette.solid.white}; + opacity: 0.7; + position: absolute; + top: 0; + ${({ position }) => { + if (position === "left") { + return ` + width: ${SIDE_PANEL_WIDTH}px; + left: 0; + `; + } + if (position === "right") { + return ` + width: ${SIDE_PANEL_WIDTH}px; + right: 0; + `; + } + if (position === "center") { + return ` + width: ${DATA_ENTRY_WIDTH}px; + `; + } + }}; + + @media only screen and (max-width: ${TWO_PANEL_MAX_WIDTH}px) { + ${({ position, currentSectionPosition }) => { + if (position === "left" && currentSectionPosition === "publishdata") { + return ` + display: none; + `; + } + if (position === "right") { + return ` + display: none; + `; + } + if (position === "center") { + return ` + margin-left: 336px; + `; + } + }}; + } + + @media only screen and (max-width: ${ONE_PANEL_MAX_WIDTH}px) { + ${({ position }) => { + if (position === "right" || position === "left") { + return ` + display: none; + `; + } + }}; + } +`; + +type OnboardingTopics = "reportsview" | "dataentryview"; + +const OnboardingIntro = ({ closeIntro }: { closeIntro: () => void }) => { + return ( + + + + + Welcome to Publisher. + + +

+ Welcome to the Justice Counts Publisher! Our team is + excited that your agency is leading the charge towards a more + transparent criminal justice system. +

+ +

+ We’ve built the Publisher Tool to make it as quick and easy for you + to publish the Justice Counts Metrics on a regular basis. Let’s walk + through the application. +

+
+ + Let’s Get Started + +
+
+ ); +}; + +type OnboardingSections = { + order: number | null; + title: string; + html: JSX.Element; + position: OnboardModalPositionProps; + action: OnboardingActionButtonProps; +}; + +const OnboardingSessionView = ({ + completeOnboarding, + topic, +}: { + completeOnboarding: () => void; + topic: OnboardingTopics; +}) => { + const [currentSectionIndex, setCurrentSectionIndex] = useState(0); + const [currentModalHeight, setCurrentModalHeight] = useState(305); + const modalHeightRef = useRef() as React.MutableRefObject; + const reportsViewSections: OnboardingSections[] = [ + { + order: 1, + title: "Reports View", + html: ( + <> +

+ Every month, we’ll generate new Reports on this + homepage. As a member of this agency’s Justice Counts team, you’ll + be able to add data for the{" "} + + Justice Counts Metrics + {" "} + via these reports whenever the relevant data is ready on your end. +

+ + ), + position: "bottomright", + action: "Next", + }, + { + order: 2, + title: "Settings", + html: ( + <> +

+ If you need to edit your name or email, click on your name and + access your Settings in the dropdown. +

+

+ Admins can also use these settings to add new team members to + Publisher. +

+ + ), + position: "bottomright", + action: "Next", + }, + { + order: 3, + title: "Feedback", + html: ( + <> +

+ Your feedback helps us to make this product even easier to use. If + you have questions, requests, or any other feedback, please email us + at{" "} + + support@justice-counts.org + {" "} + and we’ll be happy to get back to you. +

+ + ), + position: "bottomright", + action: "Finish", + }, + { + order: null, + title: "You're ready to go!", + html: ( + <> +

+ If you have any questions, email the Justice Counts team at{" "} + + support@justice-counts.org + + . +

+ + ), + position: "bottomright", + action: "Close", + }, + ]; + const dataEntryViewSections: OnboardingSections[] = [ + { + order: 1, + title: "Entering data", + html: ( + <> +

+ This is where you’ll enter the data and important contextual + information about the Justice Counts Metrics (linked{" "} + + here + {" "} + in case you need a refresher). We’ve included additional + descriptions and definitions on the side in case it’s helpful. +

+ + ), + position: "bottomright", + action: "Next", + }, + { + order: 2, + title: "Report Summary", + html: ( + <> +

+ The metrics associated with each report are listed in the Report + Summary. Checkmarks indicate that all inputs are valid; X’s indicate + that there is an error with one of the inputs. +

+
+

+ You can click on a metric to skip to that part in the form, or + scroll to the metric - either works! +

+ + ), + position: "reportsummary", + action: "Next", + }, + { + order: 3, + title: "Publish your data", + html: ( + <> +

+ Whenever you’re ready, you can publish your data with this button. + Don’t worry – you’ll still have a chance to review all the data + before it goes live*, and you can still edit the data even after + it’s been published. +

+
+

+ *NOTE: At this stage, data will just be marked as + published and will not be live just yet. +

+ + ), + position: "publishdata", + action: "Finish", + }, + { + order: null, + title: "You're ready to go!", + html: ( + <> +

+ If you have any questions, email the Justice Counts team at{" "} + + support@justice-counts.org + + . +

+ + ), + position: "bottomright", + action: "Close", + }, + ]; + const currentSections = + topic === "reportsview" ? reportsViewSections : dataEntryViewSections; + + const goToNextSection = () => + setCurrentSectionIndex((prev) => + prev + 1 < currentSections.length ? prev + 1 : prev + ); + + useEffect(() => { + if (topic === "reportsview") { + showToast( + "Welcome! Click “Next” in the onboarding boxes to continue.", + false, + undefined, + -1, + true + ); + return () => showToast("You're ready to go!", false, undefined, 0, false); + } + }, [topic]); + + useEffect(() => { + if ( + topic === "reportsview" && + currentSectionIndex + 1 === currentSections.length + ) { + showToast("You're ready to go!", false, undefined, -1, true); + } + }, [currentSectionIndex, currentSections.length, topic]); + + useEffect(() => { + if (modalHeightRef.current) { + setCurrentModalHeight( + modalHeightRef.current.getBoundingClientRect().height + ); + } + }, [currentSectionIndex]); + + return ( + <> + + + {currentSections[currentSectionIndex].title} + + + + {currentSections[currentSectionIndex].html} + + + + + {currentSectionIndex + 1 < currentSections.length && + currentSections.map( + (section, index) => + section.order && ( + setCurrentSectionIndex(index)} + /> + ) + )} + + + + + + + {topic === "dataentryview" && + currentSections[currentSectionIndex].position === "bottomright" && + currentSections[currentSectionIndex].order && ( + <> + + + + )} + + {topic === "dataentryview" && + currentSections[currentSectionIndex].position === "reportsummary" && ( + <> + + + + )} + + {topic === "dataentryview" && + currentSections[currentSectionIndex].position === "publishdata" && ( + <> + + + + )} + + ); +}; + +const Onboarding = ({ + setShowOnboarding, + topic, +}: { + setShowOnboarding: React.Dispatch>; + topic: OnboardingTopics; +}) => { + const { userStore } = useStore(); + const [showIntro, setShowIntro] = useState(true); + const closeIntro = () => setShowIntro(false); + const completeOnboarding = () => { + userStore.updateOnboardingStatus(topic, true); + setShowOnboarding(false); + }; + + /** Prevent body from scrolling when this dialog is open */ + useEffect(() => { + document.body.style.overflow = "hidden"; + return () => { + document.body.style.overflow = "unset"; + }; + }, []); + + return ( + + {showIntro && topic === "reportsview" && ( + + )} + {(!showIntro || topic === "dataentryview") && ( + + )} + + ); +}; + +export default Onboarding; diff --git a/publisher/src/components/Onboarding/OnboardingDataEntrySummary.tsx b/publisher/src/components/Onboarding/OnboardingDataEntrySummary.tsx new file mode 100644 index 000000000..1523d44c6 --- /dev/null +++ b/publisher/src/components/Onboarding/OnboardingDataEntrySummary.tsx @@ -0,0 +1,229 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { useEffect, useState } from "react"; +import styled from "styled-components/macro"; + +import closeIcon from "../assets/dark-close-icon.png"; +import { ReactComponent as Logo } from "../assets/jc-logo-vector.svg"; +import { palette, typography } from "../GlobalStyles"; +import { ONE_PANEL_MAX_WIDTH } from "../Reports/ReportDataEntry.styles"; +import { OnboardingBackdropContainer, OnboardingContainer } from "./Onboarding"; + +const OnboardingSummaryModal = styled.div` + width: 60%; + height: 76%; + background: ${palette.solid.white}; + box-shadow: inset 0px 0px 10px rgba(23, 28, 43, 0.5); + border-radius: 15px; +`; + +const ModalHeader = styled.div` + ${typography.sizeCSS.title}; + height: 119px; + display: flex; + justify-content: space-between; + position: relative; + z-index: 0; + background: linear-gradient(270deg, #016fdc 0%, #004589 100%); + box-shadow: inset 0px 0px 10px rgba(23, 28, 43, 0.5); + border-radius: 15px 15px 0 0; + padding: 35px 50px; + overflow: hidden; +`; + +const ModalBody = styled.div` + ${typography.sizeCSS.medium} + line-height: 27px; + padding: 35px 50px; + color: ${palette.solid.darkgrey}; + max-height: 76%; + overflow: scroll; + + h1 { + ${typography.sizeCSS.large} + } + + p:not(:last-child) { + margin-bottom: 18px; + } + + ul { + margin-left: 28px; + margin-bottom: 18px; + } +`; + +const CloseIcon = styled.img` + width: 36px; + height: 36px; + filter: brightness(0) invert(1); + + &:hover { + cursor: pointer; + opacity: 0.8; + } +`; + +const LogoVector = styled(Logo)` + width: 524px; + height: 524px; + position: absolute; + z-index: -1; + top: calc(50% - 524px / 2 + 114.5px); + left: 50%; + transform: translateX(-50%); + + path { + fill: rgba(1, 69, 137, 0.3); + } +`; + +export const OnboardingSummaryButton = styled.div` + width: 36px; + height: 36px; + display: flex; + justify-content: center; + align-items: center; + position: fixed; + z-index: 2; + bottom: 25px; + right: 25px; + background: ${palette.solid.blue}; + color: ${palette.solid.white}; + border-radius: 50%; + + &:hover { + cursor: pointer; + opacity: 0.8; + } + + @media only screen and (max-width: ${ONE_PANEL_MAX_WIDTH}px) { + display: none; + } +`; + +export const OnboardingDataEntrySummary: React.FC = () => { + const [showOnboardingSummary, setShowOnboardingSummary] = useState(false); + + /** Prevent body from scrolling when this dialog is open */ + useEffect(() => { + if (showOnboardingSummary) { + document.body.style.overflow = "hidden"; + } + return () => { + document.body.style.overflow = "unset"; + }; + }, [showOnboardingSummary]); + + const closeOnboardingSummary = (e: React.MouseEvent) => { + if (e.target !== e.currentTarget) return; + setShowOnboardingSummary(false); + }; + const openOnboardingSummary = () => setShowOnboardingSummary(true); + + const OnboardingSummary: React.FC = () => ( + + + + + + Data Entry + + + + +

+ + This page is where you can enter the data and important + contextual information about the Justice Counts Metrics.{" "} + +

+ +

Entering Data

+

+ The purpose of this view is to enter data for a given reporting + period. You can access all reports from the Reports View.{" "} +

+ + + Each metric is composed of three components: +
    +
  • + Primary Value: The top-level value associated + with the metric.{" "} +
  • +
  • + Breakdowns: Subdivisions of the primary value + (e.g., staff types, race, gender, etc).{" "} +
  • +
  • + Context: Additional written or predefined + fields that provide additional information about the metric.{" "} +
  • +
+
+ +

+ We’ve also included additional descriptions and definitions on the + right-side panel, including the definition of the metric, + definitions for any terms, and details on how the metric should be + calculated.{" "} +

+ +

Publishing Data

+

+ Whenever you have finished entering data, you can finalize and + publish data via the Review & Publish button.{" "} +

+ +

+ Clicking this button will first take you to a page to review the + numbers that you have entered before publishing. Publishing the + data will mark the report as “Published” in the Reports View and + the underlying data will be made available to the public via + Justice Counts publishing channels* (public dashboard, data feed, + etc). +

+

+ *NOTE: At this stage, data will just be marked as + published and will not be live just yet. +

+ +

+ After publishing, you can always go back to edit the numbers.{" "} +

+
+
+
+
+ ); + + return ( + <> + + ? + + + {showOnboardingSummary && } + + ); +}; diff --git a/publisher/src/components/Onboarding/index.ts b/publisher/src/components/Onboarding/index.ts new file mode 100644 index 000000000..96a0a180c --- /dev/null +++ b/publisher/src/components/Onboarding/index.ts @@ -0,0 +1,19 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export { default as Onboarding } from "./Onboarding"; +export * from "./OnboardingDataEntrySummary"; diff --git a/publisher/src/components/Reports/CreateReport.test.tsx b/publisher/src/components/Reports/CreateReport.test.tsx new file mode 100644 index 000000000..5546e7994 --- /dev/null +++ b/publisher/src/components/Reports/CreateReport.test.tsx @@ -0,0 +1,131 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { render, screen } from "@testing-library/react"; +import { runInAction } from "mobx"; +import React from "react"; +import { BrowserRouter } from "react-router-dom"; + +import Reports from "../../pages/Reports"; +import { rootStore, StoreProvider } from "../../stores"; + +const mockedUseNavigate = jest.fn(); +const mockedUseLocation = jest.fn(); +jest.mock("react-router-dom", () => ({ + ...jest.requireActual("react-router-dom"), + useNavigate: () => mockedUseNavigate, + useLocation: () => mockedUseLocation, +})); + +beforeEach(() => { + rootStore.reportStore.reportOverviews = {}; + rootStore.reportStore.loadingOverview = false; +}); + +test("displayed created reports", async () => { + render( + + + + ); + + await runInAction(() => { + rootStore.reportStore.reportOverviews = { + 0: { + id: 0, + agency_id: 0, + month: 11, + year: 2022, + frequency: "MONTHLY", + last_modified_at: null, + last_modified_at_timestamp: null, + editors: ["Editor #1"], + status: "NOT_STARTED", + }, + }; + }); + + const jan2022 = screen.getByText(/November 2022/i); + const editor1 = screen.getByText(/Editor #1/i); + + expect(jan2022).toBeInTheDocument(); + expect(editor1).toBeInTheDocument(); + + await runInAction(() => { + rootStore.reportStore.reportOverviews[1] = { + id: 1, + agency_id: 0, + month: 11, + year: 2020, + frequency: "ANNUAL", + last_modified_at: null, + last_modified_at_timestamp: null, + editors: ["Editor #2"], + status: "NOT_STARTED", + }; + }); + + const annualReport2020 = screen.getByText(/Annual Report 2020/i); + const editor2 = screen.getByText(/Editor #2/i); + + expect(annualReport2020).toBeInTheDocument(); + expect(editor2).toBeInTheDocument(); + + expect.hasAssertions(); +}); + +describe("test create report button", () => { + test("created reports button should not be displayed if user does not have permission", () => { + render( + + + + + + ); + + runInAction(() => { + rootStore.userStore.permissions = [""]; + }); + + const selectButton = screen.queryByText(/Select/i); + const createNewReportButton = screen.queryByText(/New/i); + + expect(selectButton).not.toBeInTheDocument(); + expect(createNewReportButton).not.toBeInTheDocument(); + }); + + test("created reports button should be displayed if user has permission", () => { + render( + + + + + + ); + + runInAction(() => { + rootStore.userStore.permissions = ["recidiviz_admin"]; + }); + + const selectButton = screen.queryByText(/Select/i); + const createNewReportButton = screen.queryByText(/New/i); + + expect(selectButton).toBeInTheDocument(); + expect(createNewReportButton).toBeInTheDocument(); + }); +}); diff --git a/publisher/src/components/Reports/CreateReport.tsx b/publisher/src/components/Reports/CreateReport.tsx new file mode 100644 index 000000000..e6c51d5fb --- /dev/null +++ b/publisher/src/components/Reports/CreateReport.tsx @@ -0,0 +1,324 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components/macro"; + +import { trackReportCreated } from "../../analytics"; +import { CreateReportFormValuesType, ReportOverview } from "../../shared/types"; +import { useStore } from "../../stores"; +import { monthsByName, printDateRangeFromMonthYear } from "../../utils"; +import { + BinaryRadioButton, + BinaryRadioGroupWrapper, + Form, + FormWrapper, + GoBackToReportsOverviewLink, + MetricSectionSubTitle, + MetricSectionTitle, + OnePanelBackLinkContainer, + PageWrapper, + PreTitle, + Title, + TitleWrapper, +} from "../Forms"; +import { Dropdown } from "../Forms/Dropdown"; +import { palette, typography } from "../GlobalStyles"; +import { showToast } from "../Toast"; +import { + PublishButton, + PublishDataWrapper, + TWO_PANEL_MAX_WIDTH, +} from "./ReportDataEntry.styles"; +import { ReportSummaryWrapper } from "./ReportSummaryPanel"; + +function createIntegerRange(start: number, end: number) { + return Array.from({ length: end - start + 1 }, (_, i) => start + i); +} + +const Heading = styled.div` + font-size: ${typography.sizeCSS.medium}; + margin-top: 24px; +`; + +const CreateReportInfoContainer = styled.div` + border-radius: 5px; + padding: 20px 30px 20px 30px; + border: 2px solid ${palette.highlight.lightblue2}; + background: ${palette.highlight.lightblue1}; + margin-top: 38px; + color: ${palette.solid.blue}; + ${typography.sizeCSS.medium} +`; + +const BoldFont = styled.span` + font-weight: 700; +`; + +const CreateButton = styled(PublishButton)` + &::after { + content: "Create Report"; + } +`; + +const FormCreateButton = styled(CreateButton)` + display: none; + width: auto; + margin-top: 48px; + + @media only screen and (max-width: ${TWO_PANEL_MAX_WIDTH}px) { + display: block; + } +`; + +const initialCreateReportFormValues: CreateReportFormValuesType = { + month: 1, + year: new Date(Date.now()).getFullYear(), + frequency: "MONTHLY", + annualStartMonth: 1, + isRecurring: false, +}; + +const CreateReport = () => { + const { reportStore, userStore } = useStore(); + const navigate = useNavigate(); + const [createReportFormValues, setCreateReportFormValues] = useState( + initialCreateReportFormValues + ); + + const updateMonth = (e: React.ChangeEvent) => + setCreateReportFormValues((prev) => ({ + ...prev, + month: +e.target.value as CreateReportFormValuesType["month"], + })); + + const updateYearStandard = (e: React.ChangeEvent) => + setCreateReportFormValues((prev) => ({ + ...prev, + annualStartMonth: +e.target + .value as CreateReportFormValuesType["annualStartMonth"], + })); + + const updateYear = (e: React.ChangeEvent) => + setCreateReportFormValues((prev) => ({ ...prev, year: +e.target.value })); + + const updateFrequency = (e: React.ChangeEvent) => + setCreateReportFormValues((prev) => ({ + ...prev, + frequency: e.target.value as CreateReportFormValuesType["frequency"], + })); + + // const updateIsRecurring = (recurring: boolean) => + // setCreateReportFormValues((prev) => ({ + // ...prev, + // isRecurring: recurring, + // })); + + const createNewReport = async () => { + const { frequency, month, year, annualStartMonth, isRecurring } = + createReportFormValues; + const response = await reportStore.createReport({ + frequency, + month: frequency === "ANNUAL" ? annualStartMonth : month, + is_recurring: isRecurring, + year: isRecurring ? new Date(Date.now()).getFullYear() : year, + }); + if (response && response instanceof Response) { + if (response.status === 200) { + navigate("/"); + showToast("The report was successfully created", true); + const report = (await response.json()) as ReportOverview; + const agency = userStore.userAgencies?.find( + (a) => a.id === report.agency_id + ); + trackReportCreated(report.id, agency); + return; + } + if (response.status === 400) { + const responseJson = await response.json(); + showToast(responseJson.description, false, "red"); + return; + } + } + showToast("Error creating report", false, "red"); + }; + + const { frequency, month, year, annualStartMonth, isRecurring } = + createReportFormValues; + + return ( + + {/* Create Report Details Panel */} + + + navigate("/")} /> + + {/* Report Details */} + + + {/* Create Report Form */} + +
+ {/* Form Title */} + + navigate("/")} /> + + Create Report + New Report + + Report Parameters + + + What reporting frequency is this report? + + + + + {createReportFormValues.frequency === "ANNUAL" && ( + <> + + What year standard do you use for annual reports? + + + + + + + )} + {/* Disable recurring report toggle for now */} + {/* TODO(#13229): Create recurring report flow */} + {/* Is this a recurring report? + + updateIsRecurring(false)} + defaultChecked + /> + updateIsRecurring(true)} + /> + */} + {createReportFormValues.isRecurring === false && ( + <> + When should this report start? + + {createReportFormValues.frequency === "MONTHLY" && ( + + {monthsByName.map((m, i) => { + return ( + + ); + })} + + )} + + + {createIntegerRange(1970, new Date().getFullYear() + 1).map( + (yr) => { + return ( + + ); + } + )} + + + + )} + + The {isRecurring ? `recurring` : ``} report + will be created for{` `} + + {printDateRangeFromMonthYear( + frequency === "ANNUAL" ? annualStartMonth : month, + isRecurring ? new Date(Date.now()).getFullYear() : year, + frequency + )} + + . + + { + e.preventDefault(); + /** Should trigger a confirmation dialogue before submitting */ + createNewReport(); + }} + /> + +
+ + {/* Create Report Review Panel */} + + + <CreateButton + onClick={() => { + /** Should trigger a confirmation dialogue before submitting */ + createNewReport(); + }} + /> + + +
+ ); +}; + +export default CreateReport; diff --git a/publisher/src/components/Reports/DataEntryForm.test.tsx b/publisher/src/components/Reports/DataEntryForm.test.tsx new file mode 100644 index 000000000..9c3673e0c --- /dev/null +++ b/publisher/src/components/Reports/DataEntryForm.test.tsx @@ -0,0 +1,228 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { render, screen, waitFor } from "@testing-library/react"; +import { runInAction } from "mobx"; +import React from "react"; +import { MemoryRouter, Route, Routes } from "react-router-dom"; + +import { rootStore, StoreProvider } from "../../stores"; +import ReportDataEntry from "./ReportDataEntry"; + +beforeEach(() => { + const mockIntersectionObserver = jest.fn(); + mockIntersectionObserver.mockReturnValue({ + observe: () => null, + unobserve: () => null, + disconnect: () => null, + }); + window.IntersectionObserver = mockIntersectionObserver; +}); + +test("display loading when no reports are loaded", async () => { + render( + + + + ); + await waitFor(async () => { + const loading = screen.getByTestId("loading"); + expect(loading).toBeInTheDocument(); + }); + expect.hasAssertions(); +}); + +test("display error when report fails to load", async () => { + render( + + + + ); + + runInAction(() => { + rootStore.userStore.userInfoLoaded = true; + }); + + const errorText = await screen.findByText( + /Error: No auth client initialized./i + ); + expect(errorText).toBeInTheDocument(); + expect.hasAssertions(); +}); + +describe("test data entry form", () => { + runInAction(() => { + rootStore.userStore.userInfoLoaded = true; + + rootStore.reportStore.reportOverviews = { + 0: { + id: 0, + agency_id: 0, + year: 2022, + month: 4, + frequency: "MONTHLY", + last_modified_at: "April 12 2022", + last_modified_at_timestamp: null, + editors: ["Editor #1", "Editor #2"], + status: "DRAFT", + }, + }; + + rootStore.reportStore.reportMetricsBySystem = { + 0: { + "Law Enforcement": [ + { + key: "PROSECUTION_STAFF", + system: "LAW_ENFORCEMENT", + display_name: "Staff", + description: + "Measures the number of full-time staff employed by the agency.", + reporting_note: "DOCs report only correctional institution staff.", + value: 1000, + unit: "people", + category: "CAPACITY_AND_COST", + label: "Total Staff", + enabled: true, + definitions: [ + { + term: "full-time staff", + definition: "definition of full-time staff", + }, + ], + contexts: [ + { + key: "PROGRAMMATIC_OR_MEDICAL_STAFF", + display_name: + "Does this include programmatic or medical staff?", + reporting_note: null, + required: false, + type: "MULTIPLE_CHOICE", + multiple_choice_options: ["YES", "NO"], + value: null, + }, + ], + disaggregations: [ + { + key: "PROSECUTION_STAFF_TYPE", + display_name: "Staff Types", + dimensions: [ + { + key: "SUPPORT", + label: "Support", + value: null, + reporting_note: "Staff: Support", + }, + ], + required: false, + helper_text: "Break down the metric by NIBRS offense types.", + }, + ], + }, + ], + }, + }; + + rootStore.reportStore.reportMetrics = { + 0: Object.values(rootStore.reportStore.reportMetricsBySystem[0]).flat(), + }; + }); + + test("displays data entry form based on reports", async () => { + render( + + + + } /> + {" "} + + + ); + + const reportDate = await screen.findByText("April 2022"); + + expect(reportDate).toBeInTheDocument(); + + const displayName = screen.getAllByText("Staff")[0]; + const metricDescription = screen.getAllByText( + "Measures the number of full-time staff employed by the agency." + )[0]; + const context = screen.getAllByText( + "Does this include programmatic or medical staff?" + )[0]; + expect(displayName).toBeInTheDocument(); + expect(metricDescription).toBeInTheDocument(); + expect(context).toBeInTheDocument(); + + expect.hasAssertions(); + }); +}); + +// TODO(#13325) JSDOM does not recognize `.animate` as a function. Will need to refactor toast or this test. + +// test("expect positive number value to not add field error (formErrors should be an empty object)", async () => { +// render( +// +// +// +// } /> +// +// +// +// ); + +// const labels = await screen.findAllByLabelText("Total Staff"); +// fireEvent.change(labels[0], { target: { value: "100" } }); +// expect( +// rootStore.formStore.metricsValues[0].PROSECUTION_STAFF.error +// ).toBeUndefined(); +// }); + +// test("expect negative number value to add field error (formErrors should contain an error property for the field)", async () => { +// render( +// +// +// +// } /> +// +// +// +// ); + +// const labels = await screen.findAllByLabelText("Total Staff"); +// fireEvent.change(labels[0], { target: { value: "-100" } }); +// expect(rootStore.formStore.metricsValues[0].PROSECUTION_STAFF.error).toBe( +// "Please enter a valid number." +// ); +// }); + +// test("expect empty value in required field to add field error (formErrors should contain an error property for the field)", async () => { +// render( +// +// +// +// } /> +// +// +// +// ); + +// const labels = await screen.findAllByLabelText("Total Staff"); +// fireEvent.change(labels[0], { target: { value: "" } }); +// expect(rootStore.formStore.metricsValues[0].PROSECUTION_STAFF.error).toBe( +// "This is a required field." +// ); +// }); diff --git a/publisher/src/components/Reports/DataEntryForm.tsx b/publisher/src/components/Reports/DataEntryForm.tsx new file mode 100644 index 000000000..63efe69a5 --- /dev/null +++ b/publisher/src/components/Reports/DataEntryForm.tsx @@ -0,0 +1,450 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { runInAction } from "mobx"; +import { observer } from "mobx-react-lite"; +import React, { Fragment, useEffect, useRef, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components/macro"; + +import { + trackAutosaveFailed, + trackAutosaveTriggered, + trackReportNotStartedToDraft, +} from "../../analytics"; +import { useStore } from "../../stores"; +import { memoizeDebounce, printReportTitle } from "../../utils"; +import { + BinaryRadioGroupClearButton, + BinaryRadioGroupContainer, + BinaryRadioGroupQuestion, + BinaryRadioGroupWrapper, + ErrorWithTooltip, + Form, + FormWrapper, + GoBackToReportsOverviewLink, + Metric, + MetricSectionSubTitle, + MetricSectionTitle, + MetricSectionTitleWrapper, + MetricSystemTitle, + NotReportedIcon, + OnePanelBackLinkContainer, + OpacityGradient, + PreTitle, + RequiredChip, + TabbedDisaggregations, + Title, +} from "../Forms"; +import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; +import { Onboarding, OnboardingDataEntrySummary } from "../Onboarding"; +import { showToast } from "../Toast"; +import { + AdditionalContextInput, + BinaryRadioButtonInputs, + MetricTextInput, +} from "./DataEntryFormComponents"; +import { + DATA_ENTRY_WIDTH, + ONE_PANEL_MAX_WIDTH, + PublishButton, + SIDE_PANEL_HORIZONTAL_PADDING, +} from "./ReportDataEntry.styles"; + +const DataEntryFormPublishButtonContainer = styled.div` + position: fixed; + display: flex; + bottom: 0; + left: 0; + right: 0; + padding: 0 ${SIDE_PANEL_HORIZONTAL_PADDING}px; + justify-content: center; + padding: 0 24px 8px; + background: ${palette.solid.white}; +`; + +const DataEntryFormPublishButton = styled(PublishButton)` + display: none; + @media only screen and (max-width: ${ONE_PANEL_MAX_WIDTH}px) { + display: block; + flex: 0 1 ${DATA_ENTRY_WIDTH}px; + } +`; + +const DataEntryFormRequiredChip = styled(RequiredChip)` + margin-right: 16px; +`; + +const DataEntryFormErrorWithTooltipContainer = styled.div` + position: absolute; + width: 100%; + top: 70px; +`; + +const AdditionalContextLabel = styled.div` + ${typography.sizeCSS.medium} + margin-top: 40px; + margin-bottom: 16px; +`; + +const DataEntryForm: React.FC<{ + reportID: number; + updateFieldDescription: (title?: string, description?: string) => void; + updateActiveMetric: (metricKey: string) => void; + toggleConfirmationDialogue: () => void; +}> = ({ + reportID, + updateFieldDescription, + updateActiveMetric, + toggleConfirmationDialogue, +}) => { + const [showOnboarding, setShowOnboarding] = useState(true); + const [hasVersionConflict, setHasVersionConflict] = useState(false); + const [scrolled, setScrolled] = useState(false); + const metricsRef = useRef([]); + const { formStore, reportStore, userStore } = useStore(); + const navigate = useNavigate(); + + const isPublished = + reportStore.reportOverviews[reportID].status === "PUBLISHED"; + + useEffect( + () => { + const handleScroll = () => { + /** To shrink the Report Title on scroll */ + if (window.scrollY > HEADER_BAR_HEIGHT) { + setScrolled(true); + } else setScrolled(false); + + /** + * To sync the Report Summary metrics list and right panel Helper Text + * to the current (mostly) visible metric. + */ + const threshold = window.innerHeight / 2; + const scrollPosition = window.scrollY + threshold; + + metricsRef.current.forEach((ref) => { + if (ref) { + const { height } = ref.getBoundingClientRect(); + const { offsetTop } = ref; + const offsetBottom = offsetTop + height; + + if (scrollPosition > offsetTop && scrollPosition < offsetBottom) { + return updateActiveMetric(ref.id); + } + } + }); + }; + + window.addEventListener("scroll", handleScroll); + return () => { + window.removeEventListener("scroll", handleScroll); + }; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + useEffect(() => { + /** Runs validation of previously saved inputs on load */ + formStore.validatePreviouslySavedInputs(reportID); + }, [formStore, reportID]); + + const saveUpdatedMetrics = async (metricKey?: string | undefined) => { + const updatedMetrics = formStore.reportUpdatedValuesForBackend( + reportID, + metricKey + ); + const oldStatus = reportStore.reportOverviews[reportID].status; + const status = + reportStore.reportOverviews[reportID].status === "PUBLISHED" + ? "PUBLISHED" + : "DRAFT"; + + showToast("Saving...", false, "grey", -1, true); + trackAutosaveTriggered(reportID); + const response = (await reportStore.updateReport( + reportID, + updatedMetrics, + status + )) as Response; + + if (response.status === 200) { + showToast("Saved", false, "grey"); + if (oldStatus === "NOT_STARTED" && status === "DRAFT") { + const agencyID = reportStore.reportOverviews[reportID]?.agency_id; + const agency = userStore.userAgencies?.find((a) => a.id === agencyID); + trackReportNotStartedToDraft(reportID, agency); + } + } else { + const body = await response.json(); + if (body.code === "version_conflict") { + showToast( + "Someone else has edited the report since you last opened it. Please refresh the page to view the latest changes and continue editing.", + false, + "red", + -1 + ); + runInAction(() => { + setHasVersionConflict(true); + }); + } else { + showToast("Failed to save", false, "red"); + } + trackAutosaveFailed(reportID); + } + }; + + const debouncedSave = useRef( + memoizeDebounce(saveUpdatedMetrics, 1500) + ).current; + + /** Saves metrics before tab/window close or page refreshes */ + useEffect( + () => { + const saveBeforeExiting = (e: BeforeUnloadEvent) => { + e.preventDefault(); + saveUpdatedMetrics(); + }; + + window.addEventListener("beforeunload", saveBeforeExiting); + return () => + window.removeEventListener("beforeunload", saveBeforeExiting); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + const reportOverview = reportStore.reportOverviews[reportID]; + const reportMetrics = reportStore.reportMetrics[reportID]; + const metricsBySystem = reportStore.reportMetricsBySystem[reportID]; + const showMetricSectionTitles = Object.keys(metricsBySystem).length > 1; + + if (!reportOverview || !reportMetrics) { + return null; + } + + return ( + +
{ + // When the form has changed, check the changed element for a `data-metric-key` + // data attribute. If present, pass to the `debouncedSave` function, which will + // then only save that metric. If not present, `metricKey` will be undefined, + // in which case `debouncedSave` will save all metrics. + const target = e.target as HTMLFormElement; + const metricKey = target.getAttribute("data-metric-key") ?? undefined; + debouncedSave(metricKey); + }} + > + {/* Form Title */} + + navigate("/")} /> + + Enter Data + + {reportOverview && + printReportTitle( + reportOverview.month, + reportOverview.year, + reportOverview.frequency + )} + + + {/* Metrics */} + {Object.entries(metricsBySystem).map( + ([system, metrics], systemIndex) => { + return ( + + {showMetricSectionTitles && ( + + {system} + + )} + + {metrics.map((metric, index) => ( + + metric.enabled && metricsRef.current?.push(e) + } + notReporting={!metric.enabled} + > + + + {metric.display_name} + + {!metric.enabled && } + + + {metric.description} + + + {metric.enabled && ( + <> + {/* Metric Value */} + + + {/* Disaggregations */} + {metric.disaggregations.length > 0 && ( + + )} + + {/* Contexts */} + {/* TODO(#13314): display multiple choice options as drop down if there are >2 options */} + {metric.contexts.length > 0 && + metric.contexts.map((context, contextIndex) => { + if (context.type === "MULTIPLE_CHOICE") { + const contextError = + formStore.contexts?.[reportID]?.[metric.key]?.[ + context.key + ]?.error; + return ( + + + {context.required && ( + + )} + {context.display_name} + + + + + + { + if ( + !isPublished && + !hasVersionConflict && + (formStore.contexts?.[reportID]?.[ + metric.key + ]?.[context.key]?.value || + context.value) + ) { + formStore.resetBinaryInput( + reportID, + metric.key, + context.key, + context.required + ); + showToast( + "Saving...", + false, + "grey", + undefined, + true + ); + debouncedSave(metric.key); + } + }} + disabled={isPublished || hasVersionConflict} + > + Clear Input + + + {/* Error */} + {contextError && ( + + + + )} + + ); + } + return ( + + + {context.display_name} + + + updateFieldDescription( + context.display_name as string, + context.reporting_note as string + ) + } + clearFieldDescription={() => + updateFieldDescription(undefined) + } + disabled={isPublished || hasVersionConflict} + /> + + ); + })} + + )} + + ))} + + ); + } + )} + + { + /** Should trigger a confirmation dialogue before submitting */ + e.preventDefault(); + toggleConfirmationDialogue(); + }} + isPublished={isPublished || hasVersionConflict} + /> + + + + + {/* Onboarding */} + + {userStore.onboardingTopicsCompleted?.dataentryview === false && + showOnboarding && ( + + )} + + +
+ ); +}; + +export default observer(DataEntryForm); diff --git a/publisher/src/components/Reports/DataEntryFormComponents.tsx b/publisher/src/components/Reports/DataEntryFormComponents.tsx new file mode 100644 index 000000000..c5fe4d754 --- /dev/null +++ b/publisher/src/components/Reports/DataEntryFormComponents.tsx @@ -0,0 +1,277 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { observer } from "mobx-react-lite"; +import React from "react"; + +import { + Metric, + MetricContext, + MetricDisaggregationDimensions, + MetricDisaggregations, +} from "../../shared/types"; +import { useStore } from "../../stores"; +import { formatNumberInput } from "../../utils"; +import { BinaryRadioButton, TextInput } from "../Forms"; + +interface MetricTextInputProps { + reportID: number; + metric: Metric; + autoFocus?: boolean; + disabled?: boolean; + updateFieldDescription?: () => void; + clearFieldDescription?: () => void; +} + +export const MetricTextInput = observer( + ({ + reportID, + metric, + autoFocus, + disabled, + updateFieldDescription, + clearFieldDescription, + }: MetricTextInputProps) => { + const { formStore } = useStore(); + const { metricsValues, updateMetricsValues } = formStore; + + const handleMetricChange = (e: React.ChangeEvent) => + updateMetricsValues(reportID, metric.key, e.target.value, metric.enabled); + + return ( + + ); + } +); + +interface DisaggregationDimensionTextInputProps extends MetricTextInputProps { + disaggregation: MetricDisaggregations; + disaggregationIndex: number; + dimension: MetricDisaggregationDimensions; + dimensionIndex: number; + disabled?: boolean; +} + +export const DisaggregationDimensionTextInput = observer( + ({ + reportID, + metric, + dimension, + disaggregation, + disaggregationIndex, + dimensionIndex, + updateFieldDescription, + clearFieldDescription, + disabled, + }: DisaggregationDimensionTextInputProps) => { + const { formStore } = useStore(); + const { disaggregations, updateDisaggregationDimensionValue } = formStore; + + const handleDisaggregationDimensionChange = ( + e: React.ChangeEvent + ) => + updateDisaggregationDimensionValue( + reportID, + metric.key, + disaggregation.key, + dimension.key, + e.target.value, + false, + metric.enabled + ); + + return ( + + ); + } +); + +interface AdditionalContextInputsProps extends MetricTextInputProps { + context: MetricContext; + contextIndex: number; +} + +interface BinaryContextProps extends AdditionalContextInputsProps { + options: string[]; +} + +export const BinaryRadioButtonInputs = observer( + ({ + reportID, + metric, + context, + contextIndex, + options, + disabled, + }: BinaryContextProps) => { + const { formStore } = useStore(); + const { contexts, updateContextValue } = formStore; + + const handleContextChange = (e: React.ChangeEvent) => { + if (!disabled) { + updateContextValue( + reportID, + metric.key, + context.key, + e.target.value, + context.required, + context.type, + metric.enabled + ); + } + }; + + return ( + <> + {options.map((option: string) => ( + + ))} + + ); + } +); + +export const AdditionalContextInput = observer( + ({ + reportID, + metric, + context, + contextIndex, + disabled, + updateFieldDescription, + clearFieldDescription, + }: AdditionalContextInputsProps) => { + const { formStore } = useStore(); + const { contexts, updateContextValue } = formStore; + const getContextValue = () => { + if ( + contexts?.[reportID]?.[metric.key]?.[context.key]?.value !== undefined + ) { + return context.type === "NUMBER" + ? formatNumberInput( + contexts[reportID]?.[metric.key][context.key].value + ) + : contexts[reportID]?.[metric.key][context.key].value; + } + + return metric.contexts[contextIndex].value?.toString() || ""; + }; + const contextValue = getContextValue(); + + const handleContextChange = ( + e: React.ChangeEvent + ) => + updateContextValue( + reportID, + metric.key, + context.key, + e.target.value, + context.required, + context.type, + metric.enabled + ); + + return ( + + ); + } +); diff --git a/publisher/src/components/Reports/HelperText.tsx b/publisher/src/components/Reports/HelperText.tsx new file mode 100644 index 000000000..8bb372b1e --- /dev/null +++ b/publisher/src/components/Reports/HelperText.tsx @@ -0,0 +1,144 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import styled from "styled-components/macro"; + +import { useStore } from "../../stores"; +import { palette, typography } from "../GlobalStyles"; +import { + BREAKPOINT_HEIGHT, + TWO_PANEL_MAX_WIDTH, +} from "./ReportDataEntry.styles"; + +const HelperTextContainer = styled.div` + height: 70vh; + overflow-y: scroll; + margin-top: 24px; + ${typography.sizeCSS.normal} + + &::-webkit-scrollbar { + -webkit-appearance: none; + width: 5px; + } + + &::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: ${palette.highlight.grey8}; + box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); + -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); + } + + @media only screen and (max-width: ${TWO_PANEL_MAX_WIDTH}px) { + height: 20vh; + padding-right: 7px; + &::-webkit-scrollbar { + -webkit-appearance: none; + width: 5px; + } + + &::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: ${palette.highlight.grey8}; + box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); + -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); + } + + @media only screen and (max-height: ${BREAKPOINT_HEIGHT}px) { + display: none; + } + } +`; + +const HelperTextSection = styled.div` + margin-bottom: 31px; + + &:last-child { + margin-bottom: 0; + } +`; + +const HelperTextTitle = styled.div` + margin-bottom: 10px; +`; + +const HelperTextContent = styled.div` + color: ${palette.highlight.grey10}; + margin-bottom: 28px; + + &:last-child { + margin-bottom: 0; + } +`; + +const HelperTextMetricName = styled.div` + ${typography.sizeCSS.medium} + margin-bottom: 6px; +`; + +const HelperTextMetricDescription = styled.div` + color: ${palette.highlight.grey10}; +`; + +const Term = styled.span` + font-weight: 600; +`; + +const HelperText: React.FC<{ + reportID: number; + activeMetric: string; +}> = ({ reportID, activeMetric }): JSX.Element | null => { + const { reportStore } = useStore(); + const currentMetric = reportStore.reportMetrics[reportID].find( + (metric) => metric.key === activeMetric + ); + + if (currentMetric === undefined) return null; + + return ( + + + + {currentMetric.display_name} + + + {currentMetric.description} + + + + + {currentMetric.definitions?.length > 0 && ( + Term Definitions + )} + {currentMetric.definitions?.map((definition) => ( + + {definition.term}: {definition.definition} + + ))} + + + {currentMetric.reporting_note && ( + + Reporting Note + {currentMetric.reporting_note} + + )} + + ); +}; + +export default HelperText; diff --git a/publisher/src/components/Reports/PublishConfirmation.tsx b/publisher/src/components/Reports/PublishConfirmation.tsx new file mode 100644 index 000000000..de26b031b --- /dev/null +++ b/publisher/src/components/Reports/PublishConfirmation.tsx @@ -0,0 +1,567 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { observer } from "mobx-react-lite"; +import React, { Fragment, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components/macro"; + +import { trackReportPublished } from "../../analytics"; +import { + MetricContextWithErrors, + MetricDisaggregationDimensionsWithErrors, + MetricDisaggregationsWithErrors, + MetricWithErrors, +} from "../../shared/types"; +import { useStore } from "../../stores"; +import { printReportTitle, rem } from "../../utils"; +import errorIcon from "../assets/status-error-icon.png"; +import { Button } from "../Forms"; +import { palette } from "../GlobalStyles"; +import { showToast } from "../Toast"; +import { PublishButton } from "./ReportDataEntry.styles"; + +const CONTAINER_WIDTH = 912; +const CONTAINER_HORIZONTAL_PADDING = 24; + +const ConfirmationDialogueWrapper = styled.div` + width: 100vw; + height: 100vh; + background: ${palette.solid.white}; + position: fixed; + top: 0; + left: 0; + z-index: 2; + padding: 80px 0; + overflow: scroll; + display: flex; + flex-direction: column; + align-items: center; +`; + +const ConfirmHeaderContainer = styled.div` + background: white; + width: 100%; + position: fixed; + background: white; + top: 0px; + z-index: 1; + padding-top: 88px; + display: flex; + flex-direction: column; + align-items: center; +`; + +const ConfirmHeader = styled.div` + max-width: ${CONTAINER_WIDTH}px; + padding: 0 ${CONTAINER_HORIZONTAL_PADDING}px; +`; + +const ConfirmationDialogue = styled.div` + max-width: ${CONTAINER_WIDTH}px; + width: 100%; + display: flex; + flex-direction: column; + text-align: left; + padding: 0 ${CONTAINER_HORIZONTAL_PADDING}px; + padding-top: 176px; +`; + +const ConfirmationTitle = styled.div` + font-size: ${rem("64px")}; + font-weight: 500; + line-height: 64px; + letter-spacing: -0.02em; +`; + +const ConfirmationSubTitle = styled.div` + flex: 1; + margin-right: 16px; + font-size: ${rem("15px")}; + line-height: 24px; + font-weight: 500; +`; + +const Metric = styled.div` + display: flex; + flex: 1 1 auto; + justify-content: space-between; + border-top: 2px solid ${palette.solid.darkgrey}; + margin-bottom: 40px; + + @media only screen and (max-width: ${CONTAINER_WIDTH + + CONTAINER_HORIZONTAL_PADDING * 2}px) { + flex-direction: column; + border-top: 0; + } +`; + +const MetricOverviewWrapper = styled.div` + flex: 0 1 330px; + display: flex; + flex-direction: column; + justify-content: flex-start; + + @media only screen and (max-width: ${CONTAINER_WIDTH + + CONTAINER_HORIZONTAL_PADDING * 2}px) { + flex: 0 1 auto; + } +`; + +const MetricDetailWrapper = styled.div` + flex: 0 1 534px; + display: flex; + flex-direction: column; + justify-content: stretch; + + @media only screen and (max-width: ${CONTAINER_WIDTH + + CONTAINER_HORIZONTAL_PADDING * 2}px) { + flex: 0 1 auto; + } +`; + +const MetricValueLabel = styled.div` + display: flex; + justify-content: flex-start; + padding-bottom: 16px; + align-items: center; + font-size: ${rem("18px")}; + line-height: 24px; + font-weight: 700; + + @media only screen and (max-width: ${CONTAINER_WIDTH + + CONTAINER_HORIZONTAL_PADDING * 2}px) { + border-bottom: 1px solid ${palette.solid.darkgrey}; + } +`; + +const MetricValue = styled.div<{ missing?: boolean; error?: boolean }>` + margin-top: 8px; + font-size: ${rem("32px")}; + line-height: 1.5; + letter-spacing: -0.01em; + color: ${({ missing, error }) => + error ? palette.solid.red : missing && palette.highlight.grey8}; +`; + +const Breakdown = styled.div<{ missing?: boolean }>` + display: flex; + justify-content: space-between; + align-items: stretch; + border-bottom: 1px dashed ${palette.solid.darkgrey}; + padding: 4px 0; + font-size: ${rem("15px")}; + line-height: 24px; + font-weight: 500; +`; + +const DisaggregationBreakdown = styled(Breakdown)` + &:first-child { + padding-top: 0; + } + + &:last-child { + margin-bottom: 24px; + } +`; + +const BreakdownLabel = styled.div` + display: flex; + flex: 1; +`; + +const BreakdownValue = styled.div<{ missing?: boolean; error?: boolean }>` + display: flex; + flex: 1; + justify-content: flex-end; + font-style: ${({ missing }) => missing && "italic"}; + color: ${({ missing, error }) => + error ? palette.solid.red : missing && palette.highlight.grey8}; +`; + +const ContextContainer = styled(Breakdown)<{ verticalOnly?: boolean }>` + border-bottom: 1px solid ${palette.solid.darkgrey}; + padding: 8px 0; + + flex-direction: ${({ verticalOnly }) => (verticalOnly ? "column" : "row")}; + + @media only screen and (max-width: ${CONTAINER_WIDTH + + CONTAINER_HORIZONTAL_PADDING * 2}px) { + flex-direction: column; + } +`; + +const ContextTitle = styled(BreakdownLabel)` + font-weight: 700; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; +`; + +const ContextValue = styled(BreakdownValue)` + justify-content: flex-start; +`; + +const ButtonContainer = styled.div` + padding: 16px 0; + display: flex; + flex: 1 1 auto; + align-items: center; + justify-content: space-between; + border-bottom: 2px solid ${palette.solid.darkgrey}; + + @media only screen and (max-width: ${CONTAINER_WIDTH + + CONTAINER_HORIZONTAL_PADDING * 2}px) { + border-bottom: 1px solid ${palette.solid.darkgrey}; + } +`; + +const PublishConfirmButton = styled(Button)` + margin-right: 8px; + flex: 1; +`; + +const TopPublishConfirmButton = styled(PublishConfirmButton)` + flex: 0 0 207px; + + @media only screen and (max-width: ${CONTAINER_WIDTH + + CONTAINER_HORIZONTAL_PADDING * 2}px) { + display: none; + } +`; + +const PublishConfirmPublishButton = styled(PublishButton)` + flex: 1; + + &::after { + content: "Publish Data"; + } +`; + +const TopPublishConfirmPublishButton = styled(PublishConfirmPublishButton)` + flex: 0 0 207px; + + @media only screen and (max-width: ${CONTAINER_WIDTH + + CONTAINER_HORIZONTAL_PADDING * 2}px) { + display: none; + } +`; + +const DisaggregationContainer = styled.div` + font-weight: 700; + font-size: ${rem("15px")}; + margin-top: 16px; + border-bottom: 1px solid ${palette.solid.darkgrey}; +`; + +const DisaggregationTitleContainer = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 16px; +`; + +const LabelLeft = styled.div` + display: inline; +`; + +const BreakdownContainer = styled.div` + padding-left: 20px; + margin-top: 8px; + border-left: 1px solid ${palette.solid.darkgrey}; +`; + +const DropdownArrowContainer = styled.div<{ collapsed?: boolean }>` + width: 16px; + height: 16px; + border-radius: 8px; + background: ${({ collapsed }) => + collapsed ? palette.highlight.grey2 : palette.highlight.lightblue2}; + display: flex; + align-items: center; + justify-content: center; + margin-left: 4px; + flex-shrink: 0; + + &:hover { + cursor: pointer; + } +`; + +const DropdownArrow = styled.div<{ collapsed?: boolean }>` + width: 6px; + height: 6px; + border: none; + border-bottom: 2px solid + ${({ collapsed }) => (collapsed ? palette.solid.grey : palette.solid.blue)}; + border-right: 2px solid + ${({ collapsed }) => (collapsed ? palette.solid.grey : palette.solid.blue)}; + transform: rotate(45deg) translate(-1px, -1px); + transform: rotate(${({ collapsed }) => (collapsed ? 225 : 45)}deg) + translate(-1px, -1px); +`; + +const ContextDropdownArrowContainer = styled(DropdownArrowContainer)` + margin-top: 3px; +`; + +const ErrorImg = styled.img` + margin-left: 4px; + width: 16px; + height: 16px; +`; + +const InnerErrorImg = styled(ErrorImg)` + transform: translate(0, 2px); +`; + +const MobileButtonContainer = styled.div` + background: ${palette.solid.white}; + position: fixed; + bottom: 0; + display: none; + justify-content: space-between; + width: 100%; + padding: 0 ${CONTAINER_HORIZONTAL_PADDING}px 8px; + + @media only screen and (max-width: ${CONTAINER_WIDTH + + CONTAINER_HORIZONTAL_PADDING * 2}px) { + display: flex; + } +`; + +const Disaggregation: React.FC<{ + disaggregation: MetricDisaggregationsWithErrors; +}> = ({ disaggregation }) => { + const [collapsed, setCollapsed] = React.useState(false); + const { display_name: displayName, dimensions } = disaggregation; + const hasError = !!dimensions.find( + (dim: MetricDisaggregationDimensionsWithErrors) => dim.error + ); + return ( + + + + {displayName} + {hasError && } + + setCollapsed(!collapsed)} + > + + + + {dimensions.length > 0 && !collapsed && ( + + {dimensions.map( + (dimension: MetricDisaggregationDimensionsWithErrors) => { + return ( + + + {dimension.label} + + {dimension.value?.toLocaleString("en-US") || + "Not Reported"} + + + + ); + } + )} + + )} + + ); +}; + +const Context: React.FC<{ context: MetricContextWithErrors }> = ({ + context, +}) => { + const [collapsed, setCollapsed] = React.useState(false); + const hasError = !!context.error; + return ( + + + + {context.display_name} + {hasError && } + + setCollapsed(!collapsed)} + > + + + + {!collapsed && ( + + {context.value?.toLocaleString("en-US") || "Not Reported"} + + )} + + ); +}; + +const MetricsDisplay: React.FC<{ + metric: MetricWithErrors; +}> = ({ metric }) => { + const hasError = !!metric.error; + return ( + + + {/* Overall Metric Value */} + + {metric.value?.toLocaleString("en-US") || "Not Reported"} + + + {metric.label} + {hasError && } + + + + + {/* Disaggregations > Dimensions */} + {metric.disaggregations.length > 0 && + metric.disaggregations.map((disaggregation) => { + return ( + + ); + })} + + {/* Contexts */} + {metric.contexts.length > 0 && + metric.contexts.map((context) => { + return ; + })} + + + ); +}; + +const PublishConfirmation: React.FC<{ + toggleConfirmationDialogue: () => void; + reportID: number; +}> = ({ toggleConfirmationDialogue, reportID }) => { + const [isPublishable, setIsPublishable] = useState(false); + const [metricsPreview, setMetricsPreview] = useState(); + const { formStore, reportStore, userStore } = useStore(); + const navigate = useNavigate(); + + const publishReport = async () => { + if (isPublishable) { + setIsPublishable(false); + + const finalMetricsToPublish = + formStore.reportUpdatedValuesForBackend(reportID); + + const response = (await reportStore.updateReport( + reportID, + finalMetricsToPublish, + "PUBLISHED" + )) as Response; + + if (response.status === 200) { + navigate("/"); + showToast( + `Congratulations! You published the ${printReportTitle( + reportStore.reportOverviews[reportID].month, + reportStore.reportOverviews[reportID].year, + reportStore.reportOverviews[reportID].frequency + )} report!`, + true + ); + const agencyID = reportStore.reportOverviews[reportID]?.agency_id; + const agency = userStore.userAgencies?.find((a) => a.id === agencyID); + trackReportPublished(reportID, finalMetricsToPublish, agency); + } else { + showToast( + `Something went wrong publishing the ${printReportTitle( + reportStore.reportOverviews[reportID].month, + reportStore.reportOverviews[reportID].year, + reportStore.reportOverviews[reportID].frequency + )} report!`, + false + ); + setIsPublishable(true); + } + } + }; + + useEffect(() => { + const { metrics, isPublishable: publishable } = + formStore.validateAndGetAllMetricFormValues(reportID); + setMetricsPreview(metrics); + setIsPublishable(publishable); + }, [formStore, reportID]); + + /** Prevent body from scrolling when this dialog is open */ + useEffect(() => { + document.body.style.overflow = "hidden"; + return () => { + document.body.style.overflow = "unset"; + }; + }, []); + + return ( + + + + Review + + + Take a moment to review the numbers that will be published. Click + the arrows to show the data for any disaggregations. + + + Back to Data Entry + + + + + + + {metricsPreview && + metricsPreview.map((metric) => { + return ; + })} + + + + Back to Data Entry + + + + + ); +}; + +export default observer(PublishConfirmation); diff --git a/publisher/src/components/Reports/PublishDataPanel.tsx b/publisher/src/components/Reports/PublishDataPanel.tsx new file mode 100644 index 000000000..d2d8b56f9 --- /dev/null +++ b/publisher/src/components/Reports/PublishDataPanel.tsx @@ -0,0 +1,65 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { observer } from "mobx-react-lite"; +import React from "react"; + +import { Title } from "../Forms"; +import HelperText from "./HelperText"; +import { + FieldDescription, + FieldDescriptionProps, + PublishButton, + PublishDataWrapper, +} from "./ReportDataEntry.styles"; + +const PublishDataPanel: React.FC<{ + reportID: number; + activeMetric: string; + fieldDescription?: FieldDescriptionProps; + toggleConfirmationDialogue: () => void; + isPublished?: boolean; +}> = ({ + reportID, + activeMetric, + fieldDescription, + toggleConfirmationDialogue, + isPublished, +}) => { + return ( + <> + + + <PublishButton + isPublished={isPublished} + onClick={() => toggleConfirmationDialogue()} + /> + + + {/* Metric Description, Definitions and Reporting Notes */} + + + {/* Displays the description of the field currently focused */} + {fieldDescription && ( + + )} + + + ); +}; + +export default observer(PublishDataPanel); diff --git a/publisher/src/components/Reports/ReportDataEntry.styles.tsx b/publisher/src/components/Reports/ReportDataEntry.styles.tsx new file mode 100644 index 000000000..b150e7734 --- /dev/null +++ b/publisher/src/components/Reports/ReportDataEntry.styles.tsx @@ -0,0 +1,99 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import styled from "styled-components/macro"; + +import { palette, typography } from "../GlobalStyles"; + +export const SIDE_PANEL_WIDTH = 360; +export const DATA_ENTRY_WIDTH = 644; +export const SIDE_PANEL_HORIZONTAL_PADDING = 24; +export const TWO_PANEL_MAX_WIDTH = DATA_ENTRY_WIDTH + SIDE_PANEL_WIDTH * 2; // data entry panel (644) + side panels (360 * 2) (each side panel includes 24px left and right padding) +export const ONE_PANEL_MAX_WIDTH = + DATA_ENTRY_WIDTH + SIDE_PANEL_WIDTH + SIDE_PANEL_HORIZONTAL_PADDING; // data entry panel (644) + left side panel (360) + right padding from the right side panel (24) +export const SINGLE_COLUMN_MAX_WIDTH = + DATA_ENTRY_WIDTH + SIDE_PANEL_HORIZONTAL_PADDING * 2; // data entry panel (644) + left and right padding (24 * 2) +export const BREAKPOINT_HEIGHT = 750; + +export const PublishDataWrapper = styled.div` + width: ${SIDE_PANEL_WIDTH}px; + position: fixed; + top: 0; + right: 0; + z-index: 1; + padding: 112px ${SIDE_PANEL_HORIZONTAL_PADDING}px 0 + ${SIDE_PANEL_HORIZONTAL_PADDING}px; + height: 100%; + + @media only screen and (max-width: ${TWO_PANEL_MAX_WIDTH}px) { + display: none; + } +`; + +export const PublishButton = styled.button<{ + disabled?: boolean; + isPublished?: boolean; +}>` + ${typography.sizeCSS.medium} + width: 315px; + height: 56px; + display: flex; + justify-content: center; + align-items: center; + background: ${({ disabled }) => (disabled ? "none" : palette.solid.blue)}; + color: ${({ disabled }) => + disabled ? palette.highlight.grey8 : palette.solid.white}; + border: 1px solid + ${({ disabled }) => + disabled ? palette.highlight.grey3 : palette.highlight.grey3}; + border-radius: 2px; + transition: 0.2s ease; + + &:hover { + cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")}; + background: ${({ disabled }) => + disabled ? "none" : palette.solid.darkblue}; + } + + &::after { + content: ${({ isPublished }) => + isPublished ? "'Unpublish and Edit'" : "'Review and Publish'"}; + } +`; + +export const FieldDescriptionTitle = styled.div` + margin-bottom: 10px; + color: ${palette.solid.darkgrey}; +`; + +export const FieldDescriptionContainer = styled.div` + ${typography.sizeCSS.normal} + padding-top: 16px; + color: ${palette.highlight.grey9}; +`; + +export type FieldDescriptionProps = { title: string; description: string }; + +export const FieldDescription: React.FC<{ + fieldDescription: FieldDescriptionProps; +}> = ({ fieldDescription }) => ( + + {fieldDescription.title} + {fieldDescription.description} + +); diff --git a/publisher/src/components/Reports/ReportDataEntry.tsx b/publisher/src/components/Reports/ReportDataEntry.tsx new file mode 100644 index 000000000..207559d06 --- /dev/null +++ b/publisher/src/components/Reports/ReportDataEntry.tsx @@ -0,0 +1,165 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { when } from "mobx"; +import { observer } from "mobx-react-lite"; +import React, { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; + +import { trackReportUnpublished } from "../../analytics"; +import { Report } from "../../shared/types"; +import { useStore } from "../../stores"; +import { printReportTitle } from "../../utils"; +import { PageWrapper } from "../Forms"; +import { Loading } from "../Loading"; +import { showToast } from "../Toast"; +import DataEntryForm from "./DataEntryForm"; +import PublishConfirmation from "./PublishConfirmation"; +import PublishDataPanel from "./PublishDataPanel"; +import { FieldDescriptionProps } from "./ReportDataEntry.styles"; +import ReportSummaryPanel from "./ReportSummaryPanel"; + +const ReportDataEntry = () => { + const [isLoading, setIsLoading] = useState(true); + const [loadingError, setLoadingError] = useState( + undefined + ); + const [activeMetric, setActiveMetric] = useState(""); + const [fieldDescription, setFieldDescription] = useState< + FieldDescriptionProps | undefined + >(undefined); + const [showConfirmation, setShowConfirmation] = useState(false); + const { reportStore, userStore } = useStore(); + const params = useParams(); + const reportID = Number(params.id); + const reportOverview = reportStore.reportOverviews[reportID] as Report; + const reportMetrics = reportStore.reportMetrics[reportID]; + const toggleConfirmationDialogue = async () => { + if (reportOverview.status === "PUBLISHED") { + const response = (await reportStore.updateReport( + reportID, + [], + "DRAFT" + )) as Response; + if (response.status === 200) { + showToast( + `The ${printReportTitle( + reportStore.reportOverviews[reportID].month, + reportStore.reportOverviews[reportID].year, + reportStore.reportOverviews[reportID].frequency + )} report has been unpublished and editing is enabled.`, + true, + undefined, + 4000 + ); + const agencyID = reportStore.reportOverviews[reportID]?.agency_id; + const agency = userStore.userAgencies?.find((a) => a.id === agencyID); + trackReportUnpublished(reportID, agency); + } else { + showToast( + `Something went wrong unpublishing the ${printReportTitle( + reportStore.reportOverviews[reportID].month, + reportStore.reportOverviews[reportID].year, + reportStore.reportOverviews[reportID].frequency + )} report!`, + false + ); + } + } else { + setShowConfirmation(!showConfirmation); + } + }; + + useEffect( + () => + // return when's disposer so it is cleaned up if it never runs + when( + () => userStore.userInfoLoaded, + async () => { + const result = await reportStore.getReport(reportID); + if (result instanceof Error) { + setLoadingError(result.message); + } + userStore.setCurrentAgencyId( + reportStore.reportOverviews[reportID]?.agency_id + ); + setIsLoading(false); + } + ), + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + + const updateActiveMetric = (metricKey: string) => setActiveMetric(metricKey); + const updateFieldDescription = (title?: string, description?: string) => { + if (title && description) { + setFieldDescription({ title, description }); + } else { + setFieldDescription(undefined); + } + }; + + useEffect(() => { + const firstEnabledMetric = reportMetrics?.find((metric) => metric.enabled); + if (reportMetrics && firstEnabledMetric) + updateActiveMetric(firstEnabledMetric.key); // open to the first enabled metric by default + }, [reportMetrics, reportID]); + + if (isLoading) { + return ( + + + + ); + } + + if (!reportMetrics || !reportOverview) { + return Error: {loadingError}; + } + + return ( + <> + + + + {showConfirmation && ( + + )} + + ); +}; + +export default observer(ReportDataEntry); diff --git a/publisher/src/components/Reports/ReportSummaryPanel.tsx b/publisher/src/components/Reports/ReportSummaryPanel.tsx new file mode 100644 index 000000000..9c2b9066b --- /dev/null +++ b/publisher/src/components/Reports/ReportSummaryPanel.tsx @@ -0,0 +1,412 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { observer } from "mobx-react-lite"; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components/macro"; + +import { Metric } from "../../shared/types"; +import { useStore } from "../../stores"; +import { + printCommaSeparatedList, + printDateRangeFromMonthYear, + printElapsedDaysMonthsYearsSinceDate, +} from "../../utils"; +import successIcon from "../assets/status-check-icon.png"; +import errorIcon from "../assets/status-error-icon.png"; +import { + GoBackToReportsOverviewLink, + MetricsSectionTitle, + PreTitle, + Title, +} from "../Forms"; +import { palette, typography } from "../GlobalStyles"; +import HelperText from "./HelperText"; +import { + BREAKPOINT_HEIGHT, + FieldDescription, + FieldDescriptionProps, + ONE_PANEL_MAX_WIDTH, + PublishButton, + SIDE_PANEL_HORIZONTAL_PADDING, + SIDE_PANEL_WIDTH, + TWO_PANEL_MAX_WIDTH, +} from "./ReportDataEntry.styles"; + +export const ReportSummaryWrapper = styled.div` + width: ${SIDE_PANEL_WIDTH}px; + height: 100%; + position: fixed; + top: 0; + left: 0; + z-index: 1; + padding: 96px ${SIDE_PANEL_HORIZONTAL_PADDING}px 0 + ${SIDE_PANEL_HORIZONTAL_PADDING}px; + background: ${palette.solid.white}; + + @media only screen and (max-width: ${ONE_PANEL_MAX_WIDTH}px) { + display: none; + } +`; + +const PUBLISH_CONFIRMATION_BUTTON_HEIGHT_AND_PADDING = 128; + +export const ReportSummaryProgressIndicatorWrapper = styled.div` + margin-top: 28px; + height: 37vh; + overflow-y: scroll; + + &::-webkit-scrollbar { + -webkit-appearance: none; + width: 5px; + } + &::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: ${palette.highlight.grey8}; + box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); + -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); + } + + @media only screen and (max-width: ${TWO_PANEL_MAX_WIDTH}px) { + height: 30vh; + } + + @media only screen and (max-height: ${BREAKPOINT_HEIGHT}px) { + height: calc(55vh - ${PUBLISH_CONFIRMATION_BUTTON_HEIGHT_AND_PADDING}px); + padding-bottom: 50px; + } +`; + +const ReportSummarySection = styled.a` + ${typography.sizeCSS.normal} + height: 24px; + display: flex; + align-items: center; + justify-content: flex-start; + position: relative; + text-decoration: none; + margin-bottom: 2px; + border-radius: 2px; + color: ${palette.highlight.grey8}; + transition: 0.2s ease; + + &:hover { + cursor: pointer; + color: ${palette.solid.darkgrey}; + } +`; + +const MetricDisplayName = styled.div<{ + activeSection?: boolean; +}>` + ${({ activeSection }) => + activeSection && `color: ${palette.solid.darkgrey};`}; + border-bottom: 2px solid + ${({ activeSection }) => + activeSection ? palette.solid.blue : `transparent`}; + max-width: 238px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +const ReportStatusIcon = styled.div<{ + metricHasError?: boolean; + metricHasEntries?: boolean; +}>` + width: 16px; + height: 16px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 100%; + margin-right: 8px; + + background: ${({ metricHasError }) => { + if (metricHasError) { + return palette.solid.red; + } + + return `transparent`; + }}; + color: white; + border: 1px solid ${palette.highlight.grey4}; +`; + +export const NotReportedHeader = styled.div` + ${typography.sizeCSS.normal} + color: ${palette.highlight.grey8}; + margin-top: 16px; + margin-bottom: 2px; +`; + +export const EditDetails = styled.div` + width: 307px; + position: fixed; + bottom: 61px; + + @media only screen and (max-width: ${TWO_PANEL_MAX_WIDTH}px) { + display: none; + } + @media only screen and (max-height: 750px) { + display: none; + } +`; + +export const EditDetailsTitle = styled.div` + ${typography.sizeCSS.small} + padding-top: 8px; + border-top: 1px solid ${palette.solid.darkgrey}; +`; + +export const EditDetailsContent = styled.div` + ${typography.sizeCSS.normal} + color: ${palette.highlight.grey9}; + margin-bottom: 18px; +`; + +const PublishContainer = styled.div` + display: none; + + @media only screen and (max-width: ${TWO_PANEL_MAX_WIDTH}px) { + display: block; + position: absolute; + border-top: 1px solid ${palette.highlight.grey9}; + background: ${palette.solid.white}; + right: 0; + bottom: 0; + left: 0; + margin: 0 24px; + padding: 24px 0; + } + + @media only screen and (max-height: ${BREAKPOINT_HEIGHT}px) { + border: none; + } +`; + +const LeftPublishButton = styled(PublishButton)` + margin-top: 24px; +`; + +const ReportStatusIconComponent: React.FC<{ + metricHasValidInput: boolean; + metricHasError: boolean; + activeMetric: string; + metric: Metric; +}> = ({ metricHasError, metricHasValidInput, metric, activeMetric }) => { + return ( + { + document + .getElementById(metric.key) + ?.scrollIntoView({ behavior: "smooth" }); + }} + > + + {/* Error State */} + {metricHasError && ( + + )} + + {/* Validated State [Placeholder] */} + {!metricHasError && metricHasValidInput && ( + + )} + + + {metric.display_name} + + + ); +}; + +const ReportSummaryPanel: React.FC<{ + reportID: number; + activeMetric: string; + fieldDescription?: FieldDescriptionProps; + toggleConfirmationDialogue: () => void; +}> = ({ + reportID, + activeMetric, + fieldDescription, + toggleConfirmationDialogue, +}) => { + const navigate = useNavigate(); + const { formStore, reportStore, userStore } = useStore(); + const { + editors, + last_modified_at: lastModifiedAt, + month, + year, + frequency, + status, + } = reportStore.reportOverviews[reportID]; + + const checkMetricForErrorsInUpdatedValues = (metricKey: string) => { + let foundErrors = false; + + if (formStore.metricsValues[reportID]?.[metricKey]?.error) { + foundErrors = true; + } + + if (formStore.disaggregations[reportID]?.[metricKey]) { + Object.values(formStore.disaggregations[reportID][metricKey]).forEach( + (disaggregation) => { + Object.values(disaggregation).forEach((dimension) => { + if (dimension.error) { + foundErrors = true; + } + }); + } + ); + } + + if (formStore.contexts[reportID]?.[metricKey]) { + Object.values(formStore.contexts[reportID][metricKey]).forEach( + (context) => { + if (context.error) { + foundErrors = true; + } + } + ); + } + + return foundErrors; + }; + + const metricsBySystem = reportStore.reportMetricsBySystem[reportID]; + const showMetricSectionTitles = Object.keys(metricsBySystem).length > 1; + + return ( + + + navigate("/")} /> + + Report Summary + + + {Object.entries(metricsBySystem).map(([system, metrics]) => { + const { enabledMetrics, disabledMetrics } = metrics.reduce<{ + enabledMetrics: Metric[]; + disabledMetrics: Metric[]; + }>( + (acc, currentMetric) => { + if (currentMetric.enabled) { + acc.enabledMetrics.push(currentMetric); + } else { + acc.disabledMetrics.push(currentMetric); + } + return acc; + }, + { enabledMetrics: [], disabledMetrics: [] } + ); + + return ( + + {showMetricSectionTitles ? ( + {system} + ) : null} + {enabledMetrics.map((metric) => { + const foundErrors = checkMetricForErrorsInUpdatedValues( + metric.key + ); + + return ( + + ); + })} + + {/* Metrics Not Reported */} + {disabledMetrics.length > 0 && ( + Not Reported + )} + {disabledMetrics.map((metric) => { + return ( + + ); + })} + + ); + })} + + + + Date Range + + {printDateRangeFromMonthYear(month, year, frequency)} + + + Editors + + {editors.length + ? printCommaSeparatedList(editors) + : userStore.nameOrEmail} + + + Details + + {editors.length === 1 && + !lastModifiedAt && + `Created today by ${editors[0]}`} + + {editors.length >= 1 && + lastModifiedAt && + `Last modified ${printElapsedDaysMonthsYearsSinceDate( + lastModifiedAt + )} by ${editors[editors.length - 1]}`} + + {!editors.length && ``} + + + + + {/* Metric Description, Definitions and Reporting Notes */} + + + {/* Displays the description of the field currently focused */} + {fieldDescription && ( + + )} + { + /** Should trigger a confirmation dialogue before submitting */ + toggleConfirmationDialogue(); + }} + /> + + + ); +}; + +export default observer(ReportSummaryPanel); diff --git a/publisher/src/components/Reports/Reports.styles.tsx b/publisher/src/components/Reports/Reports.styles.tsx new file mode 100644 index 000000000..73c1ff2d1 --- /dev/null +++ b/publisher/src/components/Reports/Reports.styles.tsx @@ -0,0 +1,254 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import styled from "styled-components/macro"; + +import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; + +const COLLAPSED_INNER_COLUMNS_WIDTH = 846; + +export const PageHeader = styled.div` + width: 100%; + background: ${palette.solid.white}; + position: fixed; + z-index: 2; +`; + +export const ReportsHeader = styled(PageHeader)` + top: ${HEADER_BAR_HEIGHT}px; +`; + +export const PageTitle = styled.div` + ${typography.sizeCSS.headline} + margin-top: 40px; + padding: 0px 22px; +`; + +export const TabbedBar = styled.div<{ noPadding?: boolean }>` + ${typography.sizeCSS.normal} + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + position: relative; + padding: ${({ noPadding }) => (noPadding ? `none` : `0px 22px`)}; + border-bottom: 1px solid ${palette.highlight.grey9}; +`; + +export const TabbedOptions = styled.div` + display: flex; + align-items: center; +`; + +export const TabbedItem = styled.div<{ + selected?: boolean; + capitalize?: boolean; +}>` + padding: 24px 0 16px 0; + margin-right: 20px; + color: ${({ selected }) => + selected ? palette.solid.darkgrey : palette.highlight.grey9}; + border-bottom: ${({ selected }) => + selected ? `3px solid ${palette.solid.blue}` : `3px solid transparent`}; + transition: color 0.3s ease; + ${({ capitalize }) => capitalize && `text-transform: capitalize;`} + + &:hover { + cursor: pointer; + color: ${palette.solid.darkgrey}; + } +`; + +export const TabbedActionsWrapper = styled.div` + display: flex; + gap: 15px; +`; + +export const ReportActions = styled.div` + display: flex; +`; + +export const ReportActionsItem = styled.div<{ + disabled?: boolean; +}>` + display: flex; + align-items: center; + margin-left: 16px; + transition: 0.2s ease; + color: ${({ disabled }) => + disabled ? palette.highlight.grey8 : palette.solid.blue}; + + &:hover { + ${({ disabled }) => + !disabled + ? ` + cursor: pointer; + opacity: 0.8; + ` + : ` + cursor: default; + `} + } +`; + +export const ReportActionsSelectIcon = styled.div<{ + disabled?: boolean; +}>` + width: 11px; + height: 11px; + display: flex; + justify-content: center; + align-items: center; + margin-left: 4.5px; + border: 1px solid + ${({ disabled }) => + disabled ? palette.highlight.grey8 : palette.solid.blue}; + border-radius: 50%; + + &::after { + content: ""; + height: 1px; + width: 6px; + background-color: ${({ disabled }) => + disabled ? palette.highlight.grey8 : palette.solid.blue}; + } +`; + +export const ReportActionsNewIcon = styled(ReportActionsSelectIcon)` + &::before { + content: ""; + position: absolute; + height: 6px; + width: 1px; + background-color: ${palette.solid.blue}; + } +`; + +export const Table = styled.div` + width: 100%; + padding: 212px 0 50px 0; +`; + +export const Row = styled.div<{ + noHover?: boolean; + selected?: boolean; +}>` + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 22px; + color: ${({ noHover }) => + noHover ? palette.highlight.grey9 : palette.solid.darkgrey}; + transition: 0.3s ease; + ${({ noHover }) => + noHover ? typography.sizeCSS.normal : typography.sizeCSS.large} + ${({ selected }) => + selected && `background-color: ${palette.solid.lightgreen};`} + + &:hover { + ${({ noHover }) => + noHover + ? `` + : `cursor: pointer; + background-color: ${palette.solid.lightgreen}; + `} + } +`; + +export const LabelRow = styled(Row)` + padding: 10px 22px; + + &:hover { + cursor: unset; + background-color: unset; + } +`; + +export const Cell = styled.div<{ capitalize?: boolean }>` + width: 100px; + display: flex; + flex: 1 4 auto; + justify-content: flex-start; + align-items: center; + position: relative; + font-size: 1.2rem; + text-transform: ${({ capitalize }) => capitalize && "capitalize"}; + padding-right: 40px; + white-space: nowrap; + + span { + padding-right: 4px; + overflow: hidden; + text-overflow: ellipsis; + } + + &:first-child { + flex: 2 1 auto; + } + + &:last-child { + flex: 2 1 auto; + justify-content: flex-end; + padding-right: unset; + } + + @media only screen and (max-width: ${COLLAPSED_INNER_COLUMNS_WIDTH}px) { + &:not(:first-child, :last-child) { + display: none; + } + } +`; + +export const LabelCell = styled(Cell)` + ${typography.sizeCSS.normal} + color: ${palette.highlight.grey9}; +`; + +export const AdditionalEditorsTooltip = styled.div` + ${typography.sizeCSS.normal} + padding: 10px 20px; + background: ${palette.solid.blue}; + color: ${palette.solid.white}; + position: absolute; + z-index: 1; + top: 32px; + border-radius: 2px; + text-align: center; + box-shadow: 2px 2px 8px ${palette.highlight.grey5}; +`; + +export const NoReportsDisplay = styled.div` + display: flex; + justify-content: center; + align-items: center; + margin-top: 50px; +`; + +export const EmptySelectionCircle = styled.div` + width: 16px; + height: 16px; + border: 1px solid ${palette.highlight.grey4}; + border-radius: 50%; + margin-right: 8px; +`; + +export const SelectedCheckmark = styled.img` + width: 16px; + height: 16px; + margin-right: 8px; +`; diff --git a/publisher/src/components/Reports/index.ts b/publisher/src/components/Reports/index.ts new file mode 100644 index 000000000..f8bb9cc3e --- /dev/null +++ b/publisher/src/components/Reports/index.ts @@ -0,0 +1,18 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./Reports.styles"; diff --git a/publisher/src/components/Toast/Toast.ts b/publisher/src/components/Toast/Toast.ts new file mode 100644 index 000000000..8143141fb --- /dev/null +++ b/publisher/src/components/Toast/Toast.ts @@ -0,0 +1,118 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import checkIconWhite from "../assets/status-check-white-icon.png"; +import { HEADER_BAR_HEIGHT, palette, typography } from "../GlobalStyles"; + +type ToastColor = "blue" | "red" | "grey"; + +export const showToast = ( + message: string, + check = false, + color: ToastColor = "blue", + timeout = 2500, + preventOverride?: boolean +) => { + const animationTransform = [{ maxWidth: "0px" }, { maxWidth: "100%" }]; + const animationTransformReverse = [ + { maxWidth: "700px" }, + { maxWidth: "0px" }, + ]; + + const activeToast = document.querySelector("#toast"); + + if (preventOverride && activeToast?.textContent === message) return; + + if (activeToast) { + activeToast.animate(animationTransformReverse, { + duration: 600, + fill: "forwards", + easing: "ease", + }); + document.body.removeChild(activeToast); + } + + const toastElementWrapper = document.createElement(`div`); + const toastElement = document.createElement(`div`); + const checkIcon = document.createElement(`img`); + toastElement.innerText = message; + toastElementWrapper.id = "toast"; + toastElementWrapper.style.cssText = ` + position: fixed; + top: 0; + left: 65px; + z-index: 100; + overflow: hidden; + `; + toastElementWrapper.appendChild(toastElement); + + let toastBackgroundColor = palette.solid.blue; + switch (color) { + case "red": + toastBackgroundColor = palette.solid.red; + break; + case "grey": + toastBackgroundColor = palette.solid.white; + break; + default: + break; + } + toastElement.style.cssText = ` + width: auto; + height: ${HEADER_BAR_HEIGHT}px; + display: flex; + align-items: center; + background: ${toastBackgroundColor}; + color: ${color === "grey" ? palette.solid.grey : palette.solid.white}; + padding: 20px 24px; + border-radius: 2px; + white-space: nowrap; + `; + checkIcon.src = checkIconWhite; + checkIcon.alt = ""; + checkIcon.style.cssText = ` + width: 16px; + height: 16px; + margin-right: 8px; + ${typography.sizeCSS.normal} + `; + + if (check) toastElement.prepend(checkIcon); + document.body.appendChild(toastElementWrapper); + + toastElementWrapper.animate(animationTransform, { + duration: 800, + fill: "forwards", + easing: "ease", + }); + + // a timeout of -1 keeps the toast from timing out + if (timeout !== -1) { + setTimeout(() => { + toastElementWrapper.animate(animationTransformReverse, { + duration: 600, + fill: "forwards", + easing: "ease", + }); + Promise.all( + toastElementWrapper + .getAnimations({ subtree: true }) + .map((animation) => animation.finished) + ).then(() => toastElementWrapper.remove()); + }, timeout); + } +}; diff --git a/publisher/src/components/Toast/index.ts b/publisher/src/components/Toast/index.ts new file mode 100644 index 000000000..f4affc669 --- /dev/null +++ b/publisher/src/components/Toast/index.ts @@ -0,0 +1,18 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./Toast"; diff --git a/publisher/src/components/assets/check-icon.svg b/publisher/src/components/assets/check-icon.svg new file mode 100644 index 000000000..c11f1c863 --- /dev/null +++ b/publisher/src/components/assets/check-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/publisher/src/components/assets/cloud-upload-icon.svg b/publisher/src/components/assets/cloud-upload-icon.svg new file mode 100644 index 000000000..0ef5b8f92 --- /dev/null +++ b/publisher/src/components/assets/cloud-upload-icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/publisher/src/components/assets/dark-close-icon.png b/publisher/src/components/assets/dark-close-icon.png new file mode 100644 index 000000000..98f9ced50 Binary files /dev/null and b/publisher/src/components/assets/dark-close-icon.png differ diff --git a/publisher/src/components/assets/download-icon.png b/publisher/src/components/assets/download-icon.png new file mode 100644 index 000000000..5f8432f2b Binary files /dev/null and b/publisher/src/components/assets/download-icon.png differ diff --git a/publisher/src/components/assets/error-icon.svg b/publisher/src/components/assets/error-icon.svg new file mode 100644 index 000000000..201a33330 --- /dev/null +++ b/publisher/src/components/assets/error-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/publisher/src/components/assets/file-icon.svg b/publisher/src/components/assets/file-icon.svg new file mode 100644 index 000000000..33ee84c52 --- /dev/null +++ b/publisher/src/components/assets/file-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/publisher/src/components/assets/info-red-icon.png b/publisher/src/components/assets/info-red-icon.png new file mode 100644 index 000000000..09d581df1 Binary files /dev/null and b/publisher/src/components/assets/info-red-icon.png differ diff --git a/publisher/src/components/assets/jc-logo-green-vector.png b/publisher/src/components/assets/jc-logo-green-vector.png new file mode 100644 index 000000000..b9cb71a0d Binary files /dev/null and b/publisher/src/components/assets/jc-logo-green-vector.png differ diff --git a/publisher/src/components/assets/jc-logo-vector-onboarding.png b/publisher/src/components/assets/jc-logo-vector-onboarding.png new file mode 100644 index 000000000..f2f3e19a2 Binary files /dev/null and b/publisher/src/components/assets/jc-logo-vector-onboarding.png differ diff --git a/publisher/src/components/assets/jc-logo-vector.png b/publisher/src/components/assets/jc-logo-vector.png new file mode 100644 index 000000000..331e8a632 Binary files /dev/null and b/publisher/src/components/assets/jc-logo-vector.png differ diff --git a/publisher/src/components/assets/jc-logo-vector.svg b/publisher/src/components/assets/jc-logo-vector.svg new file mode 100644 index 000000000..ab3dff189 --- /dev/null +++ b/publisher/src/components/assets/jc-logo-vector.svg @@ -0,0 +1,3 @@ + + + diff --git a/publisher/src/components/assets/jc-logo-vector2.png b/publisher/src/components/assets/jc-logo-vector2.png new file mode 100644 index 000000000..7783d3679 Binary files /dev/null and b/publisher/src/components/assets/jc-logo-vector2.png differ diff --git a/publisher/src/components/assets/loader-sprite-horizontal.svg b/publisher/src/components/assets/loader-sprite-horizontal.svg new file mode 100644 index 000000000..e17fd1d2c --- /dev/null +++ b/publisher/src/components/assets/loader-sprite-horizontal.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/publisher/src/components/assets/metrics-view-downright-arrow.png b/publisher/src/components/assets/metrics-view-downright-arrow.png new file mode 100644 index 000000000..05abd7efe Binary files /dev/null and b/publisher/src/components/assets/metrics-view-downright-arrow.png differ diff --git a/publisher/src/components/assets/microsoft-excel-icon.svg b/publisher/src/components/assets/microsoft-excel-icon.svg new file mode 100644 index 000000000..1677c9172 --- /dev/null +++ b/publisher/src/components/assets/microsoft-excel-icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/publisher/src/components/assets/not-reported-icon.png b/publisher/src/components/assets/not-reported-icon.png new file mode 100644 index 000000000..bdc7c3f64 Binary files /dev/null and b/publisher/src/components/assets/not-reported-icon.png differ diff --git a/publisher/src/components/assets/spreadsheet-icon.png b/publisher/src/components/assets/spreadsheet-icon.png new file mode 100644 index 000000000..e30f7b569 Binary files /dev/null and b/publisher/src/components/assets/spreadsheet-icon.png differ diff --git a/publisher/src/components/assets/status-check-icon.png b/publisher/src/components/assets/status-check-icon.png new file mode 100644 index 000000000..d053cf583 Binary files /dev/null and b/publisher/src/components/assets/status-check-icon.png differ diff --git a/publisher/src/components/assets/status-check-white-icon.png b/publisher/src/components/assets/status-check-white-icon.png new file mode 100644 index 000000000..dee3aa930 Binary files /dev/null and b/publisher/src/components/assets/status-check-white-icon.png differ diff --git a/publisher/src/components/assets/status-error-icon.png b/publisher/src/components/assets/status-error-icon.png new file mode 100644 index 000000000..daf09513e Binary files /dev/null and b/publisher/src/components/assets/status-error-icon.png differ diff --git a/publisher/src/components/assets/todo-icon.svg b/publisher/src/components/assets/todo-icon.svg new file mode 100644 index 000000000..de53d405a --- /dev/null +++ b/publisher/src/components/assets/todo-icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/publisher/src/components/assets/upload-icon.png b/publisher/src/components/assets/upload-icon.png new file mode 100644 index 000000000..1b49f2390 Binary files /dev/null and b/publisher/src/components/assets/upload-icon.png differ diff --git a/publisher/src/components/assets/warning-icon.svg b/publisher/src/components/assets/warning-icon.svg new file mode 100644 index 000000000..9759aa5c8 --- /dev/null +++ b/publisher/src/components/assets/warning-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/publisher/src/index.tsx b/publisher/src/index.tsx new file mode 100644 index 000000000..55146997f --- /dev/null +++ b/publisher/src/index.tsx @@ -0,0 +1,124 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import ReactDOM from "react-dom"; +import { BrowserRouter } from "react-router-dom"; +import { createGlobalStyle } from "styled-components/macro"; + +import App from "./App"; +import AuthWall from "./components/Auth"; +import { palette } from "./components/GlobalStyles"; +import { StoreProvider } from "./stores"; + +// load analytics +window.analytics = window.analytics || []; +const { analytics } = window; +if (!analytics.initialize) { + if (analytics.invoked) { + // eslint-disable-next-line no-console + console.error("Segment snippet included twice."); + } else { + analytics.invoked = true; + analytics.methods = [ + "trackSubmit", + "trackClick", + "trackLink", + "trackForm", + "pageview", + "identify", + "reset", + "group", + "track", + "ready", + "alias", + "debug", + "page", + "once", + "off", + "on", + "addSourceMiddleware", + "addIntegrationMiddleware", + "setAnonymousId", + "addDestinationMiddleware", + ]; + analytics.factory = function (e: unknown) { + return function () { + const t = Array.prototype.slice.call(arguments); // eslint-disable-line prefer-rest-params + t.unshift(e); + analytics.push(t); + return analytics; + }; + }; + for (let e = 0; e < analytics.methods.length; e += 1) { + const key = analytics.methods[e]; + analytics[key] = analytics.factory(key); + } + analytics.load = function (key: unknown, e: unknown) { + const t = document.createElement("script"); + t.type = "text/javascript"; + t.async = !0; + t.src = `https://cdn.segment.com/analytics.js/v1/${key}/analytics.min.js`; + const n = document.getElementsByTagName("script")[0]; + n.parentNode?.insertBefore(t, n); + analytics._loadOptions = e; // eslint-disable-line no-underscore-dangle + }; + analytics._writeKey = window.SEGMENT_KEY; // eslint-disable-line no-underscore-dangle + analytics.SNIPPET_VERSION = "4.15.3"; + analytics.load(window.SEGMENT_KEY); + analytics.page(); + } +} + +const GlobalStyle = createGlobalStyle` + * { + box-sizing: border-box; + margin: 0; + padding: 0; + } + + html, body, #root { + height: 100%; + } + + body { + width: 100%; + font-family: "Inter", sans-serif; + font-weight: 500; + font-size: 16px; + background-color: ${palette.solid.white}; + color: ${palette.solid.darkgrey}; + } + + input, textarea { + font-family: "Inter", sans-serif; + } +`; + +ReactDOM.render( + + + + + + + + + + , + document.getElementById("root") +); diff --git a/publisher/src/mocks/PreviewDataObject.tsx b/publisher/src/mocks/PreviewDataObject.tsx new file mode 100644 index 000000000..ab2aa1355 --- /dev/null +++ b/publisher/src/mocks/PreviewDataObject.tsx @@ -0,0 +1,88 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { useState } from "react"; +import styled from "styled-components/macro"; + +import { palette } from "../components/GlobalStyles"; + +const PreviewButton = styled.button<{ open?: boolean }>` + height: 80px; + width: 80px; + position: fixed; + bottom: 15px; + right: 15px; + z-index: 1000; + background: ${palette.solid.blue}; + color: ${palette.solid.white}; + border: none; + border-radius: 50%; + opacity: ${({ open }) => (open ? 1 : 0)}; + transition: 0.2s ease; + + &:hover { + cursor: pointer; + opacity: 1; + } +`; + +const PreviewDataObject: React.FC<{ + description?: string; + objectToDisplay: Record; +}> = ({ description, objectToDisplay }) => { + const [open, setOpen] = useState(false); + + return ( + <> + setOpen(!open)} open={open}> + {open ? "Close" : "Preview"} + + {open && ( +
+          

+ {description} +

+ {JSON.stringify(objectToDisplay, null, 2)} +
+ )} + + ); +}; + +export default PreviewDataObject; diff --git a/publisher/src/mocks/mockReport.ts b/publisher/src/mocks/mockReport.ts new file mode 100644 index 000000000..952ad6b38 --- /dev/null +++ b/publisher/src/mocks/mockReport.ts @@ -0,0 +1,450 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export const mockOverview = { + id: 0, + year: 2022, + month: 4, + frequency: "MONTHLY", + last_modified_at: "April 12 2022", + editors: ["Editor #1", "Editor #2"], + status: "DRAFT", +}; + +export const mockMetrics = [ + { + key: "READMISSION_RATE", + display_name: "Readmission Rate", + description: + "Measure the number of individuals admitted who had at least one other prison admission within the prior year.", + reporting_note: + "Exclude re-entry after a temporary exit (escape, work release, appointment, etc).", + value: null, + unit: "readmissions", + category: "READMISSION_RATE", + label: "Readmission Rate", + definitions: [ + { + term: "", + definition: "", + }, + ], + contexts: [ + { + key: "DEFINITION_OF_READMISSION", + display_name: "Definition of Readmission", + reporting_note: "Agency's definition of readmission.", + required: false, + type: "TEXT", + value: null, + }, + { + key: "ADDITIONAL_CONTEXT", + display_name: "Additional Context", + reporting_note: + "Add any additional context that you would like to provide here.", + required: false, + type: "TEXT", + value: null, + }, + ], + disaggregations: [ + { + key: "READMISSION_KEY_TYPE", + display_name: "Readmission Types", + dimensions: [ + { + key: "NEW_OFFENSE", + label: "New Offense", + value: null, + reporting_note: "Readmission: New Offense", + }, + { + key: "VIOLATION_OF_CONDITIONS", + label: "Violation of Conditions", + value: null, + reporting_note: "Readmission: Violation of Conditions", + }, + { + key: "OTHER", + label: "Other", + value: null, + reporting_note: "Readmission: Other", + }, + { + key: "UNKNOWN", + label: "Unknown", + value: null, + reporting_note: "Readmission: Unknown", + }, + ], + required: false, + helper_text: "Break down the metric by NIBRS offense types.", + }, + ], + }, + { + key: "ADMISSIONS", + display_name: "Admissions", + description: + "Measure the number of new admission to the state corrections system.", + reporting_note: + "Report individual in the most serious category (new sentence > vilation > hold).", + value: null, + unit: "admissions", + category: "ADMISSIONS", + label: "Admissions", + definitions: [ + { + term: "", + definition: "", + }, + ], + contexts: [ + { + key: "DEFINITION_OF_ADMISSION", + display_name: "Definition of Admission", + reporting_note: "Agency's definition of admission.", + required: false, + type: "TEXT", + value: null, + }, + { + key: "ADDITIONAL_CONTEXT", + display_name: "Additional Context", + reporting_note: + "Add any additional context that you would like to provide here.", + required: false, + type: "TEXT", + value: null, + }, + ], + disaggregations: [ + { + key: "ADMISSIONS_KEY_TYPE", + display_name: "Population Type", + dimensions: [ + { + key: "NEW_SENTENCE", + label: "New Sentence", + value: null, + reporting_note: "Admissions: New Sentence", + }, + { + key: "TRANSFER_HOLD", + label: "Transfer/Hold", + value: null, + reporting_note: "Admissions: Transfer/Hold", + }, + { + key: "SUPERVISION_VIOLATION_REVOCATION", + label: "Supervision Violation/Revocation", + value: null, + reporting_note: "Admissions: Supervision Violation/Revocation", + }, + { + key: "OTHER", + label: "Other", + value: null, + reporting_note: "Admissions: Other", + }, + { + key: "UNKNOWN", + label: "Unknown", + value: null, + reporting_note: "Admissions: Unknown", + }, + ], + required: false, + helper_text: "", + }, + ], + }, + { + key: "AVERAGE_DAILY_POPULATION", + display_name: "Average Daily Population", + description: + "Measures the average daily population held in the state corrections system.", + reporting_note: + "Calculate the average against a 30-day month. Report individual in the most serious category (new sentence > vilation > hold).", + value: null, + unit: "people", + category: "PEOPLE", + label: "People", + definitions: [ + { + term: "", + definition: "", + }, + ], + contexts: [ + { + key: "VOC_COUNT_QUESTION", + display_name: + "Are individuals admitted for violation of conditions counted within the above population categories?", + reporting_note: + "Whether individual admitted for violation of conditions are counted within or separate from the above population categories.", + required: false, + type: "MULTIPLE_CHOICE", + value: null, + }, + { + key: "ADDITIONAL_CONTEXT", + display_name: "Additional Context", + reporting_note: + "Add any additional context that you would like to provide here.", + required: false, + type: "TEXT", + value: null, + }, + ], + disaggregations: [ + { + key: "AVG_DAILY_POP_KEY_TYPE", + display_name: "Population Type", + dimensions: [ + { + key: "NEW_SENTENCE", + label: "New Sentence", + value: null, + reporting_note: "Average Daily Population: New Sentence", + }, + { + key: "TRANSFER_HOLD", + label: "Transfer/Hold", + value: null, + reporting_note: "Average Daily Population: Transfer/Hold", + }, + { + key: "SUPERVISION_VIOLATION_REVOCATION", + label: "Supervision Violation/Revocation", + value: null, + reporting_note: + "Average Daily Population: Supervision Violation/Revocation", + }, + { + key: "OTHER", + label: "Other", + value: null, + reporting_note: "Average Daily Population: Other", + }, + { + key: "UNKNOWN", + label: "Unknown", + value: null, + reporting_note: "Average Daily Population: Unknown", + }, + ], + required: false, + helper_text: "", + }, + { + key: "RACE_ETHNICITY_KEY_TYPE", + display_name: "Race & Ethnicity", + dimensions: [ + { + key: "WHITE", + label: "White", + value: null, + reporting_note: "Race/Ethnicity: White", + }, + { + key: "BLACK", + label: "Black", + value: null, + reporting_note: "Race/Ethnicity: Black", + }, + { + key: "HISPANIC_LATINX", + label: "Hispanic/Latinx", + value: null, + reporting_note: "Race/Ethnicity: Hispanic/Latinx", + }, + { + key: "ASIAN", + label: "Asian", + value: null, + reporting_note: "Race/Ethnicity: Asian", + }, + { + key: "NATIVE_AMERICAN", + label: "Native American", + value: null, + reporting_note: "Race/Ethnicity: Native American", + }, + { + key: "NATIVE_HAWAIIAN_PI", + label: "Native Hawaiian or Pacific Islander", + value: null, + reporting_note: + "Race/Ethnicity: Native Hawaiian or Pacific Islander", + }, + { + key: "OTHER", + label: "Other", + value: null, + reporting_note: "Race/Ethnicity: Other", + }, + { + key: "UNKNOWN", + label: "Unknown", + value: null, + reporting_note: "Race/Ethnicity: Unknown", + }, + ], + required: false, + helper_text: + "Measure the average daily correctional population of each race/ethnic group. This is the average daily population for each group. Calculate the average against a 30-day month.", + }, + { + key: "GENDER_KEY_TYPE", + display_name: "Gender", + dimensions: [ + { + key: "MALE", + label: "Male", + value: null, + reporting_note: "Gender: Male", + }, + { + key: "FEMALE", + label: "Female", + value: null, + reporting_note: "Gender: Female", + }, + { + key: "NON_BINARY", + label: "Non-binary", + value: null, + reporting_note: "Gender: Non-binary", + }, + { + key: "OTHER", + label: "Other", + value: null, + reporting_note: "Gender: Other", + }, + { + key: "UNKNOWN", + label: "Unknown", + value: null, + reporting_note: "Gender: Unknown", + }, + ], + required: false, + helper_text: + "Measure the average daily correctional population of each gender group. This is the average daily population for each group. Calculate the average against a 30-day month.", + }, + ], + }, + { + key: "RELEASES", + display_name: "Releases", + description: "Measure the number of releases from the facility.", + reporting_note: + "Exclude temporary release (work release, appointment, court hearing, etc).", + value: null, + unit: "releases", + category: "RELEASES", + label: "Releases", + definitions: [ + { + term: "", + definition: "", + }, + ], + contexts: [ + { + key: "DEFINITION_OF_SUPERVISION", + display_name: "Definition of Supervision", + reporting_note: + "Agency's definition of supervision (probation, parole, either).", + required: false, + type: "TEXT", + value: null, + }, + { + key: "ADDITIONAL_CONTEXT", + display_name: "Additional Context", + reporting_note: + "Add any additional context that you would like to provide here.", + required: false, + type: "TEXT", + value: null, + }, + ], + disaggregations: [ + { + key: "RELEASE_KEY_TYPE", + display_name: "Release Types", + dimensions: [ + { + key: "SENTENCE_COMPLETION", + label: "Sentence Completion", + value: null, + reporting_note: "Releases: New Sentence", + }, + { + key: "PRETRIAL_RELEASE", + label: "Pretrial Release", + value: null, + reporting_note: "Releases: Pretrial Release", + }, + { + key: "TRANSFER", + label: "Transfer", + value: null, + reporting_note: "Releases: Transfer", + }, + { + key: "UNAPPROVED ABSENCE", + label: "Unapproved Absence", + value: null, + reporting_note: "Releases: Unapproved Absence", + }, + { + key: "COMPASSIONATE", + label: "Compassionate", + value: null, + reporting_note: "Releases: Compassionate", + }, + { + key: "OTHER", + label: "Other", + value: null, + reporting_note: "Releases: Other", + }, + { + key: "UNKNOWN", + label: "Unknown", + value: null, + reporting_note: "Releases: Unknown", + }, + ], + required: false, + helper_text: "", + }, + ], + }, +]; + +export const mockReport = { + ...mockOverview, + metrics: mockMetrics, +}; + +export default mockReport; diff --git a/publisher/src/mocks/reportOverviews.json b/publisher/src/mocks/reportOverviews.json new file mode 100644 index 000000000..d53b2c8d2 --- /dev/null +++ b/publisher/src/mocks/reportOverviews.json @@ -0,0 +1,438 @@ +{ + "orderedReports": [ + { + "id": 0, + "year": 2022, + "month": 4, + "frequency": "MONTHLY", + "last_modified_at": "1648771200000", + "editors": ["Editor #1", "Editor #2"], + "status": "DRAFT" + }, + { + "id": 1, + "year": 2022, + "month": 3, + "frequency": "MONTHLY", + "last_modified_at": "1646092800000", + "editors": ["Editor #1", "Editor #2", "Editor #3"], + "status": "DRAFT" + }, + { + "id": 2, + "year": 2022, + "month": 2, + "frequency": "MONTHLY", + "last_modified_at": "1643673600000", + "editors": ["Editor #3"], + "status": "PUBLISHED" + }, + { + "id": 3, + "year": 2022, + "month": 1, + "frequency": "MONTHLY", + "last_modified_at": "1640995200000", + "editors": ["Editor #1", "Editor #2"], + "status": "PUBLISHED" + }, + { + "id": 4, + "year": 2021, + "month": 12, + "frequency": "ANNUAL", + "last_modified_at": "1640995200000", + "editors": ["Editor #1", "Editor #2", "Editor #3", "Editor #4"], + "status": "PUBLISHED" + }, + { + "id": 5, + "year": 2021, + "month": 12, + "frequency": "MONTHLY", + "last_modified_at": "1640995200000", + "editors": ["Editor #4"], + "status": "PUBLISHED" + }, + { + "id": 6, + "year": 2021, + "month": 11, + "frequency": "MONTHLY", + "last_modified_at": "1635724800000", + "editors": ["Editor #1", "Editor #2"], + "status": "PUBLISHED" + }, + { + "id": 7, + "year": 2021, + "month": 10, + "frequency": "MONTHLY", + "last_modified_at": "1633046400000", + "editors": ["Editor #1", "Editor #2", "Editor #3"], + "status": "PUBLISHED" + }, + { + "id": 8, + "year": 2021, + "month": 9, + "frequency": "MONTHLY", + "last_modified_at": "1630454400000", + "editors": ["Editor #3"], + "status": "PUBLISHED" + }, + { + "id": 9, + "year": 2021, + "month": 8, + "frequency": "MONTHLY", + "last_modified_at": "1627776000000", + "editors": ["Editor #1", "Editor #2"], + "status": "PUBLISHED" + }, + { + "id": 10, + "year": 2021, + "month": 7, + "frequency": "MONTHLY", + "last_modified_at": "", + "editors": [], + "status": "NOT_STARTED" + }, + { + "id": 11, + "year": 2021, + "month": 6, + "frequency": "MONTHLY", + "last_modified_at": "1622505600000", + "editors": ["Editor #1", "Editor #2"], + "status": "DRAFT" + }, + { + "id": 12, + "year": 2021, + "month": 5, + "frequency": "MONTHLY", + "last_modified_at": "1619827200000", + "editors": ["Editor #1", "Editor #2", "Editor #3"], + "status": "DRAFT" + }, + { + "id": 13, + "year": 2021, + "month": 4, + "frequency": "MONTHLY", + "last_modified_at": "1617235200000", + "editors": ["Editor #3"], + "status": "PUBLISHED" + }, + { + "id": 14, + "year": 2021, + "month": 3, + "frequency": "MONTHLY", + "last_modified_at": "1614556800000", + "editors": ["Editor #1", "Editor #2"], + "status": "PUBLISHED" + }, + { + "id": 15, + "year": 2021, + "month": 2, + "frequency": "MONTHLY", + "last_modified_at": "", + "editors": [], + "status": "NOT_STARTED" + }, + { + "id": 16, + "year": 2021, + "month": 1, + "frequency": "MONTHLY", + "last_modified_at": "1609459200000", + "editors": ["Editor #1", "Editor #2"], + "status": "DRAFT" + }, + { + "id": 17, + "year": 2020, + "month": 12, + "frequency": "ANNUAL", + "last_modified_at": "", + "editors": [], + "status": "NOT_STARTED" + }, + { + "id": 18, + "year": 2020, + "month": 12, + "frequency": "MONTHLY", + "last_modified_at": "", + "editors": [], + "status": "NOT_STARTED" + }, + { + "id": 19, + "year": 2020, + "month": 11, + "frequency": "MONTHLY", + "last_modified_at": "1604188800000", + "editors": ["Editor #1", "Editor #2"], + "status": "DRAFT" + }, + { + "id": 20, + "year": 2020, + "month": 10, + "frequency": "MONTHLY", + "last_modified_at": "1601510400000", + "editors": ["Editor #1", "Editor #2", "Editor #3"], + "status": "DRAFT" + }, + { + "id": 21, + "year": 2020, + "month": 9, + "frequency": "MONTHLY", + "last_modified_at": "1598918400000", + "editors": ["Editor #3"], + "status": "PUBLISHED" + }, + { + "id": 22, + "year": 2020, + "month": 8, + "frequency": "MONTHLY", + "last_modified_at": "1596240000000", + "editors": ["Editor #1", "Editor #2"], + "status": "PUBLISHED" + }, + { + "id": 23, + "year": 2019, + "month": 7, + "frequency": "ANNUAL", + "last_modified_at": "", + "editors": [], + "status": "NOT_STARTED" + } + ], + "unorderedReports": [ + { + "id": 1, + "year": 2022, + "month": 3, + "frequency": "MONTHLY", + "last_modified_at": "1646092800000", + "editors": ["Editor #1", "Editor #2", "Editor #3"], + "status": "DRAFT" + }, + { + "id": 22, + "year": 2020, + "month": 8, + "frequency": "MONTHLY", + "last_modified_at": "1596240000000", + "editors": ["Editor #1", "Editor #2"], + "status": "PUBLISHED" + }, + { + "id": 3, + "year": 2022, + "month": 1, + "frequency": "MONTHLY", + "last_modified_at": "1640995200000", + "editors": ["Editor #1", "Editor #2"], + "status": "PUBLISHED" + }, + { + "id": 5, + "year": 2021, + "month": 12, + "frequency": "MONTHLY", + "last_modified_at": "1640995200000", + "editors": ["Editor #4"], + "status": "PUBLISHED" + }, + { + "id": 11, + "year": 2021, + "month": 6, + "frequency": "MONTHLY", + "last_modified_at": "1622505600000", + "editors": ["Editor #1", "Editor #2"], + "status": "DRAFT" + }, + { + "id": 6, + "year": 2021, + "month": 11, + "frequency": "MONTHLY", + "last_modified_at": "1635724800000", + "editors": ["Editor #1", "Editor #2"], + "status": "PUBLISHED" + }, + { + "id": 0, + "year": 2022, + "month": 4, + "frequency": "MONTHLY", + "last_modified_at": "1648771200000", + "editors": ["Editor #1", "Editor #2"], + "status": "DRAFT" + }, + { + "id": 7, + "year": 2021, + "month": 10, + "frequency": "MONTHLY", + "last_modified_at": "1633046400000", + "editors": ["Editor #1", "Editor #2", "Editor #3"], + "status": "PUBLISHED" + }, + { + "id": 19, + "year": 2020, + "month": 11, + "frequency": "MONTHLY", + "last_modified_at": "1604188800000", + "editors": ["Editor #1", "Editor #2"], + "status": "DRAFT" + }, + { + "id": 9, + "year": 2021, + "month": 8, + "frequency": "MONTHLY", + "last_modified_at": "1627776000000", + "editors": ["Editor #1", "Editor #2"], + "status": "PUBLISHED" + }, + { + "id": 10, + "year": 2021, + "month": 7, + "frequency": "MONTHLY", + "last_modified_at": "", + "editors": [], + "status": "NOT_STARTED" + }, + { + "id": 12, + "year": 2021, + "month": 5, + "frequency": "MONTHLY", + "last_modified_at": "1619827200000", + "editors": ["Editor #1", "Editor #2", "Editor #3"], + "status": "DRAFT" + }, + { + "id": 13, + "year": 2021, + "month": 4, + "frequency": "MONTHLY", + "last_modified_at": "1617235200000", + "editors": ["Editor #3"], + "status": "PUBLISHED" + }, + { + "id": 8, + "year": 2021, + "month": 9, + "frequency": "MONTHLY", + "last_modified_at": "1630454400000", + "editors": ["Editor #3"], + "status": "PUBLISHED" + }, + { + "id": 14, + "year": 2021, + "month": 3, + "frequency": "MONTHLY", + "last_modified_at": "1614556800000", + "editors": ["Editor #1", "Editor #2"], + "status": "PUBLISHED" + }, + { + "id": 15, + "year": 2021, + "month": 2, + "frequency": "MONTHLY", + "last_modified_at": "", + "editors": [], + "status": "NOT_STARTED" + }, + { + "id": 2, + "year": 2022, + "month": 2, + "frequency": "MONTHLY", + "last_modified_at": "1643673600000", + "editors": ["Editor #3"], + "status": "PUBLISHED" + }, + { + "id": 16, + "year": 2021, + "month": 1, + "frequency": "MONTHLY", + "last_modified_at": "1609459200000", + "editors": ["Editor #1", "Editor #2"], + "status": "DRAFT" + }, + { + "id": 18, + "year": 2020, + "month": 12, + "frequency": "MONTHLY", + "last_modified_at": "", + "editors": [], + "status": "NOT_STARTED" + }, + { + "id": 20, + "year": 2020, + "month": 10, + "frequency": "MONTHLY", + "last_modified_at": "1601510400000", + "editors": ["Editor #1", "Editor #2", "Editor #3"], + "status": "DRAFT" + }, + { + "id": 4, + "year": 2021, + "month": 12, + "frequency": "ANNUAL", + "last_modified_at": "1640995200000", + "editors": ["Editor #1", "Editor #2", "Editor #3", "Editor #4"], + "status": "PUBLISHED" + }, + { + "id": 21, + "year": 2020, + "month": 9, + "frequency": "MONTHLY", + "last_modified_at": "1598918400000", + "editors": ["Editor #3"], + "status": "PUBLISHED" + }, + { + "id": 17, + "year": 2020, + "month": 12, + "frequency": "ANNUAL", + "last_modified_at": "", + "editors": [], + "status": "NOT_STARTED" + }, + { + "id": 23, + "year": 2019, + "month": 7, + "frequency": "ANNUAL", + "last_modified_at": "", + "editors": [], + "status": "NOT_STARTED" + } + ] +} diff --git a/publisher/src/pages/AccountSettings.tsx b/publisher/src/pages/AccountSettings.tsx new file mode 100644 index 000000000..7ad5e48c0 --- /dev/null +++ b/publisher/src/pages/AccountSettings.tsx @@ -0,0 +1,99 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components/macro"; + +import { + Button, + GoBack, + TextInput, + Title, + TitleWrapper, +} from "../components/Forms"; +import { useStore } from "../stores"; + +const AccountSettingsPage = styled.div` + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +const SettingsFormPanel = styled.div` + width: 644px; +`; + +const ButtonWrapper = styled.div` + display: flex; + flex: 1 1 auto; + justify-content: space-between; +`; + +const AccountSettings = () => { + const { userStore } = useStore(); + const navigate = useNavigate(); + const [email, setEmail] = React.useState(userStore?.email || ""); + const [name, setName] = React.useState(userStore?.name || ""); + return ( + + navigate(-1)} + /> + + + Settings + + + setName(e.target.value)} + /> + setEmail(e.target.value)} + /> + + + + + + + + ); +}; + +export default AccountSettings; diff --git a/publisher/src/pages/Reports.tsx b/publisher/src/pages/Reports.tsx new file mode 100644 index 000000000..37c58b1ca --- /dev/null +++ b/publisher/src/pages/Reports.tsx @@ -0,0 +1,379 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { reaction, when } from "mobx"; +import { observer } from "mobx-react-lite"; +import React, { Fragment, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import checkmarkIcon from "../components/assets/status-check-icon.png"; +import { Badge, BadgeColorMapping } from "../components/Badge"; +import { Loading } from "../components/Loading"; +import { Onboarding } from "../components/Onboarding"; +import { + AdditionalEditorsTooltip, + Cell, + EmptySelectionCircle, + LabelCell, + LabelRow, + NoReportsDisplay, + PageTitle, + ReportActions, + ReportActionsItem, + ReportActionsNewIcon, + ReportActionsSelectIcon, + ReportsHeader, + Row, + SelectedCheckmark, + TabbedActionsWrapper, + TabbedBar, + TabbedItem, + TabbedOptions, + Table, +} from "../components/Reports"; +import { Permission, ReportOverview } from "../shared/types"; +import { useStore } from "../stores"; +import { + normalizeString, + printCommaSeparatedList, + printElapsedDaysMonthsYearsSinceDate, + printReportTitle, + removeSnakeCase, +} from "../utils"; + +enum ReportStatusFilterOption { + AllReports = "All Reports", + Draft = "Draft", + Published = "Published", + NotStarted = "Not_Started", +} + +const reportListColumnTitles = [ + "Report Period", + "Frequency", + "Editors", + "Last Modified", +]; + +const Reports: React.FC = () => { + const { reportStore, userStore, datapointsStore } = useStore(); + const navigate = useNavigate(); + + const [showOnboarding, setShowOnboarding] = useState(true); + const [loadingError, setLoadingError] = useState( + undefined + ); + const [showAdditionalEditorsTooltip, setShowAdditionalEditorsTooltip] = + useState(); + const [reportsFilter, setReportsFilter] = useState("allreports"); + const [selectionMode, setSelectionMode] = useState(false); + const [reportsToDelete, setReportsToDelete] = useState([]); + + const enterSelectionMode = () => setSelectionMode(true); + const exitSelectionMode = () => setSelectionMode(false); + const clearAllReportsToDelete = () => setReportsToDelete([]); + const addOrRemoveReportToDelete = (reportID: number) => + setReportsToDelete((prev) => + !prev.includes(reportID) + ? [...prev, reportID] + : prev.filter((id) => id !== reportID) + ); + + const filterReportsBy = ( + e: React.MouseEvent + ): void => { + const { id } = e.target as HTMLDivElement; + const normalizedID = normalizeString(id); + setReportsFilter(normalizedID); + }; + + const renderReportYearRow = ( + filteredReports: ReportOverview[], + currentIndex: number, + currentReportYear: number + ): JSX.Element | undefined => { + const indexIsLessThanListOfReports = + currentIndex + 1 < filteredReports.length; + const nextReportYear = + indexIsLessThanListOfReports && filteredReports[currentIndex + 1].year; + + if (indexIsLessThanListOfReports && nextReportYear !== currentReportYear) { + return {nextReportYear}; + } + }; + + // load report overviews after the /api/users request returns successfully + useEffect( + () => + // return when's disposer so it is cleaned up if it never runs + when( + () => userStore.userInfoLoaded, + async () => { + const result = await reportStore.getReportOverviews(); + if (result instanceof Error) { + setLoadingError(result.message); + } + } + ), + [reportStore, userStore] + ); + + // reload report overviews when the current agency ID changes + useEffect( + () => + // return disposer so it is cleaned up if it never runs + reaction( + () => userStore.currentAgencyId, + async (currentAgencyId, previousAgencyId) => { + // prevents us from calling getReportOverviews twice on initial load + if (previousAgencyId !== undefined) { + reportStore.resetState(); + datapointsStore.resetState(); + const result = await reportStore.getReportOverviews(); + if (result instanceof Error) { + setLoadingError(result.message); + } + } + } + ), + [reportStore, userStore, datapointsStore] + ); + + const filteredReportsMemoized = React.useMemo( + () => + reportsFilter === "allreports" + ? reportStore.reportOverviewList + : reportStore.reportOverviewList.filter( + (report) => normalizeString(report.status) === reportsFilter + ), + [reportStore.reportOverviewList, reportsFilter] + ); + + const renderReports = (userHasNoAgency: boolean) => { + if (reportStore.loadingOverview) { + return ; + } + if (loadingError) { + return {`Error: ${loadingError}`}; + } + + const reportStatusBadgeColors: BadgeColorMapping = { + DRAFT: "ORANGE", + PUBLISHED: "GREEN", + NOT_STARTED: "RED", + }; + + return ( + <> + {filteredReportsMemoized.length > 0 ? ( + filteredReportsMemoized.map( + (report: ReportOverview, index: number) => ( + + { + if (!selectionMode) { + navigate(`/reports/${report.id}`); + } else { + addOrRemoveReportToDelete(report.id); + } + }} + selected={ + selectionMode && reportsToDelete.includes(report.id) + } + > + {/* Report Period */} + + {selectionMode && ( + <> + {reportsToDelete.includes(report.id) ? ( + + ) : ( + + )} + + )} + + {printReportTitle( + report.month, + report.year, + report.frequency + )} + + + {removeSnakeCase(report.status).toLowerCase()} + + + + {/* Status */} + {report.frequency.toLowerCase()} + + {/* Editors */} + { + if (report.editors.length > 1) { + setShowAdditionalEditorsTooltip(report.id); + } + }} + onMouseLeave={() => + setShowAdditionalEditorsTooltip(undefined) + } + > + {report.editors.length === 0 ? ( + "-" + ) : ( + <> + {report.editors[0]} + {report.editors.length > 1 + ? `& ${report.editors.length - 1} other${ + report.editors.length > 2 ? "s" : "" + }` + : ``} + + {showAdditionalEditorsTooltip === report.id && ( + + {printCommaSeparatedList(report.editors)} + + )} + + )} + + + {/* Last Modified */} + + {!report.last_modified_at + ? "-" + : printElapsedDaysMonthsYearsSinceDate( + report.last_modified_at + )} + + + + {/* Report Year Marker */} + {renderReportYearRow( + filteredReportsMemoized, + index, + report.year + )} + + ) + ) + ) : ( + + {userHasNoAgency + ? "It looks like no agency is tied to this account. Please reach out to the Justice Counts team for assistance." + : "No reports to display."} + + )} + + ); + }; + + return ( + <> + + Reports + + {/* Filter Reports By */} + + + {Object.values(ReportStatusFilterOption).map((option) => ( + filterReportsBy(e)} + > + {removeSnakeCase(option)} + + ))} + + + + {/* Admin Only: Manage Reports */} + {userStore.permissions.includes(Permission.RECIDIVIZ_ADMIN) && ( + <> + + {!selectionMode && ( + <> + + Select + + navigate("/reports/create")} + > + New + + + )} + + {selectionMode && ( + <> + { + if (reportsToDelete.length > 0) { + reportStore.deleteReports(reportsToDelete); + exitSelectionMode(); + clearAllReportsToDelete(); + } + }} + > + Delete{" "} + + + { + exitSelectionMode(); + clearAllReportsToDelete(); + }} + > + Done + + + )} + + + )} + + + + {/* Labels */} + + {reportListColumnTitles.map((title) => ( + {title} + ))} + + + + {/* Reports List Table */} + {renderReports(userStore.userAgencies?.length === 0)}
+ + {/* Onboarding */} + {userStore.onboardingTopicsCompleted?.reportsview === false && + showOnboarding && ( + + )} + + ); +}; + +export default observer(Reports); diff --git a/publisher/src/react-app-env.d.ts b/publisher/src/react-app-env.d.ts new file mode 100644 index 000000000..6431bc5fc --- /dev/null +++ b/publisher/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/publisher/src/setupTests.ts b/publisher/src/setupTests.ts new file mode 100644 index 000000000..47215a52d --- /dev/null +++ b/publisher/src/setupTests.ts @@ -0,0 +1,24 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import "@testing-library/jest-dom"; + +import { enableFetchMocks } from "jest-fetch-mock"; + +enableFetchMocks(); + +window.APP_CONFIG = { + domain: "", + client_id: "", + audience: "", +}; + +// polyfill for when running jest tests +/* eslint-disable no-extend-native */ +if (typeof String.prototype.replaceAll === "undefined") { + String.prototype.replaceAll = function (match, replace) { + return this.replace(new RegExp(match, "g"), () => replace as string); + }; +} +/* eslint-enable no-extend-native */ diff --git a/publisher/src/shared/types.ts b/publisher/src/shared/types.ts new file mode 100644 index 000000000..b8ad3d31f --- /dev/null +++ b/publisher/src/shared/types.ts @@ -0,0 +1,275 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export enum Permission { + RECIDIVIZ_ADMIN = "recidiviz_admin", + SWITCH_AGENCIES = "switch_agencies", +} + +export type AgencySystems = + | "LAW_ENFORCEMENT" + | "PROSECUTION" + | "DEFENSE" + | "COURTS_AND_PRETRIAL" + | "JAILS" + | "PRISONS" + | "SUPERVISION" + | "PAROLE" + | "PROBATION" + | "POST_RELEASE"; + +export interface UserAgency { + name: string; + id: number; + fips_county_code: string; + state_code: string; + system: AgencySystems; + systems: AgencySystems[]; +} + +export type ReportFrequency = "MONTHLY" | "ANNUAL"; + +export type ReportStatus = "NOT_STARTED" | "DRAFT" | "PUBLISHED"; + +export interface ReportOverview { + id: number; + agency_id: number; + month: number; + year: number; + frequency: ReportFrequency; + last_modified_at: string | null; + // TODO(#14138): Backend should only send timestamps + last_modified_at_timestamp: number | null; + editors: string[]; + status: ReportStatus; +} + +export interface Report extends ReportOverview { + metrics: Metric[]; +} + +export type MetricWithErrors = Metric & { + error?: string; + contexts: MetricContextWithErrors[]; + disaggregations: MetricDisaggregationsWithErrors[]; +}; + +export type MetricContextWithErrors = MetricContext & { + error?: string; +}; + +export type MetricDisaggregationsWithErrors = MetricDisaggregations & { + dimensions: MetricDisaggregationDimensionsWithErrors[]; +}; + +export type MetricDisaggregationDimensionsWithErrors = + MetricDisaggregationDimensions & { + error?: string; + }; + +export interface Metric { + key: string; + system: AgencySystems; + display_name: string; + description: string; + reporting_note: string; + value: string | number | boolean | null | undefined; + unit: string; + category: string; + label: string; + definitions: MetricDefinition[]; + contexts: MetricContext[]; + disaggregations: MetricDisaggregations[]; + enabled?: boolean; +} + +export interface MetricDefinition { + term: string; + definition: string; +} + +export interface MetricContext { + key: string; + display_name: string | null | undefined; + reporting_note: string | null | undefined; + required: boolean; + type: "TEXT" | "NUMBER" | "MULTIPLE_CHOICE"; + value: string | number | boolean | null | undefined; + multiple_choice_options: string[]; +} + +export interface MetricDisaggregations { + key: string; + display_name: string; + dimensions: MetricDisaggregationDimensions[]; + required: boolean; + helper_text: string | null | undefined; + enabled?: boolean; +} + +export interface MetricDisaggregationDimensions { + key: string; + label: string; + value: string | number | boolean | null | undefined; + reporting_note: string; + enabled?: boolean; +} + +export interface CreateReportFormValuesType extends Record { + month: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + year: number; + frequency: ReportFrequency; + annualStartMonth: number; + isRecurring: boolean; +} + +export interface FormError { + message: string; + info?: string; +} + +export interface FormContexts { + [contextKey: string]: { value?: string; error?: FormError }; +} + +export interface FormDimensions { + [dimensionKey: string]: { value?: string; error?: FormError }; +} + +export interface FormDisaggregations { + [disaggregationKey: string]: FormDimensions; +} + +export interface FormMetric { + value: string | number; + contexts: FormContexts; + disaggregations: FormDisaggregations; +} + +export interface FormReport { + [metricKey: string]: FormMetric; +} + +export interface FormStoreMetricValue { + [metricKey: string]: { value?: string; error?: FormError }; +} +export interface FormStoreMetricValues { + [reportID: string]: FormStoreMetricValue; +} + +export interface FormStoreContextValue { + [metricKey: string]: FormContexts; +} +export interface FormStoreContextValues { + [reportID: string]: FormStoreContextValue; +} + +export interface FormStoreDisaggregationValue { + [metricKey: string]: FormDisaggregations; +} +export interface FormStoreDisaggregationValues { + [reportID: string]: FormStoreDisaggregationValue; +} + +export interface UpdatedMetricsValues { + key: string; + value: Metric["value"]; + contexts: { key: string; value: MetricContext["value"] }[]; + disaggregations: { + key: string; + dimensions: { + key: string; + value: MetricDisaggregationDimensions["value"]; + }[]; + }[]; +} + +/** + * Reports data that comes in from the server. + * This closely resembles how report data is stored in our backend. + */ +export interface RawDatapoint { + id: number; + report_id: number | null; + start_date: string; + end_date: string; + metric_definition_key: string; + metric_display_name: string | null; + disaggregation_display_name: string | null; + dimension_display_name: string | null; + value: string; + is_published: boolean; + frequency: ReportFrequency; +} + +/** + * A Datapoint is an object representing a piece of justice counts metrics data for rendering in Recharts. + * Currently we only render Stacked Bar Charts. + * Each Datapoint represents a bar on the bar chart. + * Each Datapoint has: + * • a unique start_date and end_date, which serve as the x-axis category, + * • the frequency of the reporting data, either monthly or annual + * • "dataVizMissingData" which is used to render the missing data bar if there are no metrics for the time range represented + * • remaning keys which store the name of a piece of the stacked bar chart and its value. + * + * For example, raw datapoints that look like {start_date: "1/2020", disaggregation: "Gender", dimension: "Male", value: 5}, + * {start_date: "1/2020", disaggregation: "Gender", dimension: "Female", value: 3}, would be combined into + * {start_date: "1/2020", "Male": 5, "Female": 3} + * and keyed by "Gender". + */ +export interface Datapoint { + start_date: string; + end_date: string; + frequency: ReportFrequency; + // dataVizMissingData is used to render the missing data bar if there are no values reported for that time range + dataVizMissingData: number; + // the value here should really be number | null but Typescript doesn't allow for this easily + [dimensionOrAggregatedTotal: string]: string | number | null; +} + +export interface DatapointsGroupedByAggregateAndDisaggregations { + aggregate: Datapoint[]; + disaggregations: { + [disaggregation: string]: { + [start_date: string]: Datapoint; + }; + }; +} + +export interface DatapointsByMetric { + [metricKey: string]: DatapointsGroupedByAggregateAndDisaggregations; +} + +export type DataVizTimeRange = 0 | 6 | 12 | 60 | 120; + +export const DataVizTimeRangesMap: { [key: string]: DataVizTimeRange } = { + All: 0, + "6 Months Ago": 6, + "1 Year Ago": 12, + "5 Years Ago": 60, + "10 Years Ago": 120, +}; + +export type DatapointsViewSetting = "Count" | "Percentage"; + +export interface DimensionNamesByMetricAndDisaggregation { + [metric: string]: { + [disaggregation: string]: string[]; + }; +} + +export const DataVizAggregateName = "Total"; diff --git a/publisher/src/stores/API.test.tsx b/publisher/src/stores/API.test.tsx new file mode 100644 index 000000000..99f81f7bc --- /dev/null +++ b/publisher/src/stores/API.test.tsx @@ -0,0 +1,75 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= +import API from "./API"; + +const mockFetch = fetch as jest.Mock; +const MockAuthStore = jest.fn(() => { + return { + getToken: () => "token", + }; +}) as jest.Mock; + +afterEach(() => { + jest.resetAllMocks(); +}); + +describe("testing API calls", () => { + const api = new API(MockAuthStore()); + + test("error calling protected api", async () => { + mockFetch.mockRejectedValue(new Error("Failed to fetch")); + + await expect( + api.request({ + path: "/api/hello", + method: "GET", + }) + ).rejects.toThrow("Failed to fetch"); + expect(fetch).toBeCalledTimes(1); + }); + + test("successfully calling protected api", async () => { + mockFetch.mockResolvedValue({ + json: () => + Promise.resolve({ + message: "The API successfully validated your access token.", + }), + }); + + const response = (await api.request({ + path: "/api", + method: "GET", + })) as Response; + const data = await response.json(); + + expect(data.message).toBe( + "The API successfully validated your access token." + ); + expect(fetch).toBeCalledTimes(1); + + expect.hasAssertions(); + }); + + test("call to protected api has authorization header", async () => { + await expect(api.request({ path: "/api", method: "POST" })).rejects.toThrow( + Error + ); + expect(mockFetch.mock.calls[0][1].headers).toHaveProperty("Authorization"); + + expect.hasAssertions(); + }); +}); diff --git a/publisher/src/stores/API.ts b/publisher/src/stores/API.ts new file mode 100644 index 000000000..8f67b89d4 --- /dev/null +++ b/publisher/src/stores/API.ts @@ -0,0 +1,150 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { makeAutoObservable, runInAction, when } from "mobx"; + +import { trackLoadTime, trackNetworkError } from "../analytics"; +import { AuthStore } from "../components/Auth"; +import { showToast } from "../components/Toast"; + +export interface RequestProps { + path: string; + method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; + body?: FormData | Record; + retrying?: boolean; +} + +class API { + authStore: AuthStore; + + isSessionInitialized: boolean; + + csrfToken: string; + + constructor(authStore: AuthStore) { + makeAutoObservable(this); + + this.authStore = authStore; + this.isSessionInitialized = false; + this.csrfToken = ""; + + when( + () => authStore.isAuthorized, + () => this.initSession() + ); + } + + async initSession(): Promise { + try { + const response = (await this.request({ + path: "/api/init", + method: "GET", + })) as Response; + const { csrf } = await response.json(); + + runInAction(() => { + if (csrf !== "") this.csrfToken = csrf; + this.isSessionInitialized = true; + }); + } catch (error) { + if (error instanceof Error) return error.message; + return String(error); + } + } + + async request({ + path, + method, + body, + retrying = false, + }: RequestProps): Promise { + try { + const startTime = Date.now(); + if (!this.authStore.getToken) { + return Promise.reject(); + } + + const token = await this.authStore.getToken(); + + // Files are sent as FormData and not JSON + const jsonOrFormDataBody = + body instanceof FormData ? body : JSON.stringify(body); + + const headers: HeadersInit = { + Authorization: `Bearer ${token}`, + "X-CSRF-Token": this.csrfToken, + }; + + if (!(body instanceof FormData)) { + headers["Content-Type"] = "application/json"; + } + + const response = await fetch(path, { + body: method !== "GET" ? jsonOrFormDataBody : null, + method, + headers, + }); + + if (response.status >= 400) { + if (!retrying) { + const responseText = await response.clone().text(); + + if (responseText.includes("The CSRF token has expired.")) { + await this.initSession(); + return runInAction(() => + this.request({ path, method, body, retrying: true }) + ); + } + } + + const responseCopy = response.clone(); + const responseJson = await responseCopy.json(); + trackNetworkError( + path, + method, + response.status, + responseJson.description + ); + } else { + const loadTime = Date.now() - startTime; + trackLoadTime(path, method, loadTime); + } + + return response; + } catch (error) { + if (error instanceof Error) { + trackNetworkError(path, method, 0, error.message); + if (error.message.includes("Login required")) { + showToast( + "Your session has expired. Redirecting you to the login page...", + false, + "red" + ); + // Wait before reloading so user has a chance to see the toast + setTimeout(() => { + window.location.reload(); + }, 1000); + } + throw error; + } + trackNetworkError(path, method, 0, String(error)); + throw error; + } + } +} + +export default API; diff --git a/publisher/src/stores/DatapointsStore.ts b/publisher/src/stores/DatapointsStore.ts new file mode 100644 index 000000000..c1cd4f029 --- /dev/null +++ b/publisher/src/stores/DatapointsStore.ts @@ -0,0 +1,155 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { makeAutoObservable, runInAction } from "mobx"; + +import { + DatapointsByMetric, + DataVizAggregateName, + DimensionNamesByMetricAndDisaggregation, + RawDatapoint, +} from "../shared/types"; +import { isPositiveNumber } from "../utils"; +import API from "./API"; +import UserStore from "./UserStore"; + +class DatapointsStore { + userStore: UserStore; + + api: API; + + rawDatapoints: RawDatapoint[]; + + dimensionNamesByMetricAndDisaggregation: DimensionNamesByMetricAndDisaggregation; + + loading: boolean; + + constructor(userStore: UserStore, api: API) { + makeAutoObservable(this); + + this.api = api; + this.userStore = userStore; + this.rawDatapoints = []; + this.dimensionNamesByMetricAndDisaggregation = {}; + this.loading = true; + } + + /** + * Transforms raw data from the server into Datapoints keyed by metric, + * grouped by aggregate values and disaggregations. + * Aggregate is an array of objects each containing start_date, end_date, and the aggregate value. + * Disaggregations are keyed by disaggregation name and each value is an object + * with the key being the start_date and the value being an object + * containing start_date, end_date and key value pairs for each dimension and their values. + * See the DatapointsByMetric type for details. + */ + get datapointsByMetric(): DatapointsByMetric { + return this.rawDatapoints.reduce((res: DatapointsByMetric, dp) => { + if (!res[dp.metric_definition_key]) { + res[dp.metric_definition_key] = { + aggregate: [], + disaggregations: {}, + }; + } + + const sanitizedValue = + dp.value !== null && isPositiveNumber(dp.value) + ? Number(dp.value) + : null; + + if ( + dp.disaggregation_display_name === null || + dp.dimension_display_name === null + ) { + res[dp.metric_definition_key].aggregate.push({ + [DataVizAggregateName]: sanitizedValue, + start_date: dp.start_date, + end_date: dp.end_date, + frequency: dp.frequency, + dataVizMissingData: 0, + }); + } else { + if ( + !res[dp.metric_definition_key].disaggregations[ + dp.disaggregation_display_name + ] + ) { + res[dp.metric_definition_key].disaggregations[ + dp.disaggregation_display_name + ] = {}; + } + res[dp.metric_definition_key].disaggregations[ + dp.disaggregation_display_name + ][dp.start_date] = { + ...res[dp.metric_definition_key].disaggregations[ + dp.disaggregation_display_name + ][dp.start_date], + start_date: dp.start_date, + end_date: dp.end_date, + [dp.dimension_display_name]: sanitizedValue, + frequency: dp.frequency, + dataVizMissingData: 0, + }; + } + return res; + }, {}); + } + + async getDatapoints(): Promise { + try { + const { currentAgency } = this.userStore; + if (currentAgency === undefined) { + // If user is not attached to an agency, + // no need to bother trying to load this data. + runInAction(() => { + this.loading = false; + }); + return; + } + const response = (await this.api.request({ + path: `/api/agencies/${currentAgency.id}/datapoints`, + method: "GET", + })) as Response; + if (response.status === 200) { + const result = await response.json(); + runInAction(() => { + this.rawDatapoints = result.datapoints; + this.dimensionNamesByMetricAndDisaggregation = + result.dimension_names_by_metric_and_disaggregation; + }); + } else { + const error = await response.json(); + throw new Error(error.description); + } + } catch (error) { + runInAction(() => { + this.loading = false; + }); + if (error instanceof Error) return new Error(error.message); + } + } + + resetState() { + // reset the state + runInAction(() => { + this.rawDatapoints = []; + this.loading = true; + }); + } +} + +export default DatapointsStore; diff --git a/publisher/src/stores/FormStore.test.tsx b/publisher/src/stores/FormStore.test.tsx new file mode 100644 index 000000000..72e6543b8 --- /dev/null +++ b/publisher/src/stores/FormStore.test.tsx @@ -0,0 +1,153 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { runInAction } from "mobx"; + +import { rootStore } from "."; + +const { reportStore, formStore } = rootStore; + +beforeEach(() => { + runInAction(() => { + reportStore.reportOverviews = { + 0: { + id: 0, + agency_id: 0, + year: 2022, + month: 4, + frequency: "MONTHLY", + last_modified_at: "April 12 2022", + last_modified_at_timestamp: null, + editors: ["Editor #1", "Editor #2"], + status: "DRAFT", + }, + }; + + reportStore.reportMetrics = { + 0: [ + { + key: "PROSECUTION_STAFF", + system: "LAW_ENFORCEMENT", + display_name: "Staff", + description: + "Measures the number of full-time staff employed by the agency.", + reporting_note: "DOCs report only correctional institution staff.", + value: 1000, + unit: "people", + category: "CAPACITY_AND_COST", + label: "Total Staff", + definitions: [ + { + term: "full-time staff", + definition: "definition of full-time staff", + }, + ], + contexts: [ + { + key: "PROGRAMMATIC_OR_MEDICAL_STAFF", + display_name: "Does this include programmatic or medical staff?", + reporting_note: null, + required: false, + type: "MULTIPLE_CHOICE", + multiple_choice_options: ["YES", "NO"], + value: null, + }, + ], + disaggregations: [ + { + key: "PROSECUTION_STAFF_TYPE", + display_name: "Staff Types", + dimensions: [ + { + key: "SUPPORT", + label: "Support", + value: null, + reporting_note: "Staff: Support", + }, + ], + required: false, + helper_text: "Break down the metric by NIBRS offense types.", + }, + ], + }, + ], + }; + }); +}); + +test("metrics value handler updates the metric value", () => { + formStore.updateMetricsValues(0, "PROSECUTION_STAFF", "2000", true); + + expect(formStore.metricsValues[0].PROSECUTION_STAFF.value).toEqual("2000"); + + expect.hasAssertions(); +}); + +test("disaggregation dimension value handler updates the disaggregation dimension value", () => { + formStore.updateDisaggregationDimensionValue( + 0, + "PROSECUTION_STAFF", + "PROSECUTION_STAFF_TYPE", + "SUPPORT", + "200", + false, + true + ); + + expect( + formStore.disaggregations[0].PROSECUTION_STAFF.PROSECUTION_STAFF_TYPE + .SUPPORT.value + ).toEqual("200"); + + expect.hasAssertions(); +}); + +test("context value handler updates the context value", () => { + formStore.updateContextValue( + 0, + "PROSECUTION_STAFF", + "PROGRAMMATIC_OR_MEDICAL_STAFF", + "100", + false, + "NUMBER", + true + ); + + expect( + formStore.contexts[0].PROSECUTION_STAFF.PROGRAMMATIC_OR_MEDICAL_STAFF.value + ).toEqual("100"); + + expect.hasAssertions(); +}); + +test("updatedReportValues maps all updated (and not updated) input values into required data structure", () => { + expect(JSON.stringify(formStore.reportUpdatedValuesForBackend(0))).toEqual( + JSON.stringify([ + { + key: "PROSECUTION_STAFF", + value: 2000, + contexts: [{ key: "PROGRAMMATIC_OR_MEDICAL_STAFF", value: 100 }], + disaggregations: [ + { + key: "PROSECUTION_STAFF_TYPE", + dimensions: [{ key: "SUPPORT", value: 200 }], + }, + ], + }, + ]) + ); +}); diff --git a/publisher/src/stores/FormStore.ts b/publisher/src/stores/FormStore.ts new file mode 100644 index 000000000..8a4303c95 --- /dev/null +++ b/publisher/src/stores/FormStore.ts @@ -0,0 +1,558 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { makeAutoObservable } from "mobx"; + +import { + FormError, + FormStoreContextValues, + FormStoreDisaggregationValues, + FormStoreMetricValues, + Metric, + UpdatedMetricsValues, +} from "../shared/types"; +import { + isPositiveNumber, + normalizeToString, + removeCommaSpaceAndTrim, + sanitizeInputValue, +} from "../utils"; +import ReportStore from "./ReportStore"; + +class FormStore { + reportStore: ReportStore; + + metricsValues: FormStoreMetricValues; + + contexts: FormStoreContextValues; + + disaggregations: FormStoreDisaggregationValues; + + constructor(reportStore: ReportStore) { + makeAutoObservable(this); + + this.reportStore = reportStore; + this.metricsValues = {}; + this.contexts = {}; + this.disaggregations = {}; + } + + validatePreviouslySavedInputs(reportID: number) { + /** Runs validation of previously saved inputs on load */ + this.reportStore.reportMetrics[reportID].forEach((metric) => { + if (metric.value !== null && metric.value !== undefined) { + this.updateMetricsValues( + reportID, + metric.key, + normalizeToString(metric.value), + metric.enabled + ); + } + + metric.disaggregations.forEach((disaggregation) => { + disaggregation.dimensions.forEach((dimension) => { + if (dimension.value !== null && dimension.value !== undefined) { + this.updateDisaggregationDimensionValue( + reportID, + metric.key, + disaggregation.key, + dimension.key, + normalizeToString(dimension.value), + disaggregation.required, + metric.enabled + ); + } + }); + }); + + metric.contexts.forEach((context) => { + if (context.value !== null && context.value !== undefined) { + this.updateContextValue( + reportID, + metric.key, + context.key, + normalizeToString(context.value), + context.required, + context.type, + metric.enabled + ); + } + }); + }); + } + + isMetricEmpty(reportID: number, metricKey: string) { + let isEmpty = true; + const metricToCheck = this.reportStore.reportMetrics[reportID].find( + (metric) => metric.key === metricKey + ); + + if (this.metricsValues[reportID]?.[metricKey]?.value) { + return false; + } + + metricToCheck?.contexts.forEach((context) => { + if (this.contexts[reportID]?.[metricKey]?.[context.key]?.value) { + isEmpty = false; + } + }); + + metricToCheck?.disaggregations.forEach((disaggregation) => { + disaggregation.dimensions.forEach((dimension) => { + if ( + this.disaggregations[reportID]?.[metricKey]?.[disaggregation.key]?.[ + dimension.key + ]?.value + ) { + isEmpty = false; + } + }); + }); + + return isEmpty; + } + + validateAndGetAllMetricFormValues(reportID: number): { + metrics: Metric[]; + isPublishable: boolean; + } { + let isPublishable = true; + let errorFound = false; + let allMetricsAreEmpty = true; + + const updatedMetrics = this.reportStore.reportMetrics[reportID]?.map( + (metric) => { + const metricValues = this.metricsValues[reportID]?.[metric.key]; + const contexts = this.contexts[reportID]?.[metric.key]; + const disaggregationForMetric = + this.disaggregations[reportID]?.[metric.key]; + const metricIsEmpty = this.isMetricEmpty(reportID, metric.key); + + if (metricIsEmpty) { + return metric; + } + + allMetricsAreEmpty = false; + + /** Touch & validate metric field */ + if (metricValues?.value !== "") { + this.updateMetricsValues( + reportID, + metric.key, + normalizeToString(metricValues?.value) || + normalizeToString(metric.value), + metric.enabled + ); + } + + const metricError = this.metricsValues[reportID]?.[metric.key]?.error; + + if (metricError) { + errorFound = true; + } + + return { + ...metric, + value: sanitizeInputValue(metricValues?.value, metric.value), + error: metricError, + contexts: metric.contexts.map((context) => { + /** Touch & validate context field */ + if (metricValues?.value !== "") { + this.updateContextValue( + reportID, + metric.key, + context.key, + normalizeToString(contexts?.[context.key]?.value) || + normalizeToString(context.value), + context.required, + context.type, + metric.enabled + ); + } + + const contextError = + this.contexts[reportID]?.[metric.key]?.[context.key]?.error; + + if (contextError) { + errorFound = true; + } + + return { + ...context, + value: sanitizeInputValue( + contexts?.[context.key]?.value, + context.value, + context.type + ), + error: contextError, + }; + }), + disaggregations: metric.disaggregations.map((disaggregation) => { + return { + ...disaggregation, + dimensions: disaggregation.dimensions?.map((dimension) => { + const disaggregationError = + this.disaggregations[reportID]?.[metric.key]?.[ + disaggregation.key + ]?.[dimension.key]?.error; + + if (disaggregationError) { + errorFound = true; + } + + return { + ...dimension, + value: sanitizeInputValue( + disaggregationForMetric?.[disaggregation.key]?.[ + dimension.key + ]?.value, + dimension.value + ), + error: disaggregationError, + }; + }), + }; + }), + }; + } + ); + + if (errorFound || allMetricsAreEmpty) { + isPublishable = false; + } + + return { metrics: updatedMetrics || [], isPublishable }; + } + + /** + * Maps updated values into data structure required by the backend. + * Backend requires a combination of updated values on updated fields, + * and default values (the ones retrieved from the backend on load) for + * fields that have not been updated. + * + * @returns updated array of metrics (in the required data structure) + */ + + reportUpdatedValuesForBackend( + reportID: number, + metricKey?: string + ): UpdatedMetricsValues[] { + const allMetrics = this.reportStore.reportMetrics[reportID]; + // If `metricKey` is not undefined, only update the corresponding metric. + // Else, update all metrics. + const metricsToUpdate = + metricKey !== undefined + ? this.reportStore.reportMetrics[reportID].filter( + (metric) => metric.key === metricKey + ) + : allMetrics; + const updatedMetricValues = metricsToUpdate?.map((metric) => { + /** Note: all empty inputs will be represented by null */ + const metricValue = sanitizeInputValue( + this.metricsValues[reportID]?.[metric.key]?.value, + metric.value + ); + + const combinedMetricValues: UpdatedMetricsValues = { + key: metric.key, + value: metricValue, + contexts: [], + disaggregations: [], + }; + + metric.contexts.forEach((context) => { + const contextValue = sanitizeInputValue( + this.contexts[reportID]?.[metric.key]?.[context.key]?.value, + context.value, + context.type + ); + + combinedMetricValues.contexts.push({ + key: context.key, + value: contextValue, + }); + }); + + metric.disaggregations.forEach((disaggregation) => { + combinedMetricValues.disaggregations.push({ + key: disaggregation.key, + dimensions: disaggregation.dimensions.map((dimension) => { + const dimensionValue = sanitizeInputValue( + this.disaggregations[reportID]?.[metric.key]?.[ + disaggregation.key + ]?.[dimension.key]?.value, + dimension.value + ); + + return { + key: dimension.key, + value: dimensionValue, + }; + }), + }); + }); + + return combinedMetricValues; + }); + + return updatedMetricValues || []; + } + + validate = ( + validationType: string, + value: string, + required: boolean, + reportID: number, + metricKey: string, + key1?: string, + key2?: string + ) => { + const cleanValue = removeCommaSpaceAndTrim(value); + const isRequiredButEmpty = required && cleanValue === ""; + const metricIsEmpty = this.isMetricEmpty(reportID, metricKey); + + const updateFieldErrorMessage = ( + operation: "ADD" | "DELETE" | "ADD TO METRIC", + error?: FormError + ) => { + /** + * Overall metric: !key1 && !key2 + * Context: key1 && !key2 + * Disaggregation Dimension: key1 && key2 + */ + if (operation === "ADD") { + if (key1 && key2) { + this.disaggregations[reportID][metricKey][key1][key2].error = error; + } else if (key1 && !key2) { + this.contexts[reportID][metricKey][key1].error = error; + } else { + this.metricsValues[reportID][metricKey].error = error; + } + } + if (operation === "ADD TO METRIC") { + this.metricsValues[reportID][metricKey].error = error; + } + if (operation === "DELETE") { + if (key1 && key2) { + delete this.disaggregations[reportID][metricKey][key1][key2].error; + } else if (key1 && !key2) { + delete this.contexts[reportID][metricKey][key1].error; + } else { + delete this.metricsValues[reportID][metricKey].error; + } + } + }; + + if (metricIsEmpty) { + if (this.metricsValues?.[reportID]?.[metricKey]?.error) + delete this.metricsValues[reportID][metricKey].error; + + if (this.contexts?.[reportID]?.[metricKey]) + Object.keys(this.contexts[reportID][metricKey]).forEach( + (contextKey) => { + delete this.contexts[reportID][metricKey][contextKey].error; + } + ); + + updateFieldErrorMessage("DELETE"); + return; + } + + if (key1 && !this.metricsValues?.[reportID]?.[metricKey]?.value) { + if (!this.metricsValues[reportID]) { + this.metricsValues[reportID] = {}; + } + if (!this.metricsValues[reportID][metricKey]) { + this.metricsValues[reportID][metricKey] = {}; + } + updateFieldErrorMessage("ADD TO METRIC", { + message: "You are also required to enter a value for this field.", + info: "Because you have entered data for this metric, you are also required to fill out this value. If you do not have this data, please leave all fields in this metric (including disaggregations and contexts) blank.", + }); + } + + /** Raise Error */ + if (isRequiredButEmpty) { + updateFieldErrorMessage("ADD", { + message: "You are also required to enter a value for this field.", + info: "Because you have entered data for this metric, you are also required to fill out this value. If you do not have this data, please leave all fields in this metric (including disaggregations and contexts) blank.", + }); + return; + } + + if (!required && cleanValue === "") { + /** Remove Error */ + updateFieldErrorMessage("DELETE"); + return; + } + + if (validationType === "NUMBER") { + /** Raise Error */ + if (!isPositiveNumber(cleanValue)) { + updateFieldErrorMessage("ADD", { + message: "Please enter a valid number.", + }); + return; + } + } + + /** Remove Error */ + updateFieldErrorMessage("DELETE"); + }; + + updateMetricsValues = ( + reportID: number, + metricKey: string, + updatedValue: string, + metricEnabled: boolean | undefined + ): void => { + /** + * Create an empty object within the property if none exist to improve access + * speed and to help with isolating re-renders for each form component. + */ + if (!this.metricsValues[reportID]) { + this.metricsValues[reportID] = {}; + } + if (!this.metricsValues[reportID][metricKey]) { + this.metricsValues[reportID][metricKey] = {}; + } + + this.metricsValues[reportID][metricKey].value = updatedValue; + if (metricEnabled) { + this.validate("NUMBER", updatedValue, true, reportID, metricKey); + } + }; + + updateDisaggregationDimensionValue = ( + reportID: number, + metricKey: string, + disaggregationKey: string, + dimensionKey: string, + updatedValue: string, + required: boolean, + metricEnabled: boolean | undefined + ): void => { + /** + * Create empty objects within the properties if none exist to improve access + * speed and to help with isolating re-renders for each form component. + */ + if (!this.disaggregations[reportID]) { + this.disaggregations[reportID] = {}; + } + + if (!this.disaggregations[reportID][metricKey]) { + this.disaggregations[reportID][metricKey] = {}; + } + + if (!this.disaggregations[reportID][metricKey][disaggregationKey]) { + this.disaggregations[reportID][metricKey][disaggregationKey] = {}; + } + + if ( + !this.disaggregations[reportID][metricKey][disaggregationKey][ + dimensionKey + ] + ) { + this.disaggregations[reportID][metricKey][disaggregationKey][ + dimensionKey + ] = {}; + } + + this.disaggregations[reportID][metricKey][disaggregationKey][ + dimensionKey + ].value = updatedValue; + + if (metricEnabled) { + this.validate( + "NUMBER", + updatedValue, + required, + reportID, + metricKey, + disaggregationKey, + dimensionKey + ); + } + }; + + updateContextValue = ( + reportID: number, + metricKey: string, + contextKey: string, + updatedValue: string, + required: boolean, + contextType: string, + metricEnabled: boolean | undefined + ): void => { + /** + * Create an empty object within the property if none exist to improve access + * speed and to help with isolating re-renders for each form component. + */ + if (!this.contexts[reportID]) { + this.contexts[reportID] = {}; + } + + if (!this.contexts[reportID][metricKey]) { + this.contexts[reportID][metricKey] = {}; + } + + if (!this.contexts[reportID][metricKey][contextKey]) { + this.contexts[reportID][metricKey][contextKey] = {}; + } + + this.contexts[reportID][metricKey][contextKey].value = updatedValue; + + if (metricEnabled) { + this.validate( + contextType, + updatedValue, + required, + reportID, + metricKey, + contextKey + ); + } + }; + + resetBinaryInput = ( + reportID: number, + metricKey: string, + contextKey: string, + required: boolean + ): void => { + /** + * Create an empty object within the property if none exist to improve access + * speed and to help with isolating re-renders for each form component. + */ + if (!this.contexts[reportID]) { + this.contexts[reportID] = {}; + } + + if (!this.contexts[reportID][metricKey]) { + this.contexts[reportID][metricKey] = {}; + } + + if (!this.contexts[reportID][metricKey][contextKey]) { + this.contexts[reportID][metricKey][contextKey] = {}; + } + + this.contexts[reportID][metricKey][contextKey].value = ""; + this.validate("TEXT", "", required, reportID, metricKey, contextKey); + }; +} + +export default FormStore; diff --git a/publisher/src/stores/ReportStore.test.tsx b/publisher/src/stores/ReportStore.test.tsx new file mode 100644 index 000000000..07212a837 --- /dev/null +++ b/publisher/src/stores/ReportStore.test.tsx @@ -0,0 +1,107 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { render, screen } from "@testing-library/react"; +import { runInAction } from "mobx"; +import React from "react"; + +import mockJSON from "../mocks/reportOverviews.json"; +import Reports from "../pages/Reports"; +import { ReportOverview } from "../shared/types"; +import { rootStore, StoreProvider } from "."; + +const mockUnorderedReportsMap: { [reportID: string]: ReportOverview } = {}; +(mockJSON.unorderedReports as ReportOverview[]).forEach((report) => { + mockUnorderedReportsMap[report.id] = report; +}); + +const mockedUseNavigate = jest.fn(); +const mockedUseLocation = jest.fn(); +jest.mock("react-router-dom", () => ({ + ...jest.requireActual("react-router-dom"), + useNavigate: () => mockedUseNavigate, + useLocation: () => mockedUseLocation, +})); + +beforeEach(() => { + rootStore.reportStore.reportOverviews = {}; + rootStore.reportStore.getReportOverviews = () => Promise.resolve(); +}); + +test("sort in reportOverviewList", () => { + runInAction(() => { + rootStore.reportStore.reportOverviews = mockUnorderedReportsMap; + }); + + expect(JSON.stringify(rootStore.reportStore.reportOverviewList)).toEqual( + JSON.stringify(mockJSON.orderedReports) + ); +}); + +test("loading reports", async () => { + render( + + + + ); + + const loader = screen.getByTestId("loading"); + expect(loader).toBeInTheDocument(); + + expect.hasAssertions(); +}); + +test("no reports to display", async () => { + render( + + + + ); + + runInAction(() => { + rootStore.userStore.userInfoLoaded = true; + rootStore.reportStore.loadingOverview = false; + rootStore.reportStore.reportOverviews = {}; + }); + + const noReportsLoaded = await screen.findByText(/No reports to display./i); + expect(noReportsLoaded).toBeInTheDocument(); + expect.hasAssertions(); +}); + +test("displayed reports", async () => { + render( + + + + ); + + runInAction(() => { + rootStore.reportStore.loadingOverview = false; + rootStore.reportStore.reportOverviews = mockUnorderedReportsMap; + }); + + // Arbitrary report dates included in mockJSON + const april2022 = await screen.findByText(/April 2022/i); + const december2020 = await screen.findByText(/December 2020/i); + const annualReport2019 = await screen.findByText(/Annual Report 2019/i); + expect(april2022).toBeInTheDocument(); + expect(december2020).toBeInTheDocument(); + expect(annualReport2019).toBeInTheDocument(); + + expect.hasAssertions(); +}); diff --git a/publisher/src/stores/ReportStore.ts b/publisher/src/stores/ReportStore.ts new file mode 100644 index 000000000..3ae812d70 --- /dev/null +++ b/publisher/src/stores/ReportStore.ts @@ -0,0 +1,377 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { makeAutoObservable, runInAction } from "mobx"; + +import { UploadedFileStatus } from "../components/DataUpload"; +import { MetricSettings } from "../components/MetricsView"; +import { + Metric, + Report, + ReportOverview, + ReportStatus, + UpdatedMetricsValues, +} from "../shared/types"; +import { groupBy } from "../utils/helperUtils"; +import API from "./API"; +import UserStore from "./UserStore"; + +class ReportStore { + userStore: UserStore; + + api: API; + + reportOverviews: { [reportID: string]: ReportOverview }; // key by report ID + + reportMetrics: { [reportID: string]: Metric[] }; // key by report ID + + reportMetricsBySystem: { [reportID: string]: { [system: string]: Metric[] } }; // key by report ID, then by system + + loadingOverview: boolean; + + constructor(userStore: UserStore, api: API) { + makeAutoObservable(this); + + this.api = api; + this.userStore = userStore; + this.reportOverviews = {}; + this.reportMetrics = {}; + this.reportMetricsBySystem = {}; + this.loadingOverview = true; + } + + get reportOverviewList(): ReportOverview[] { + return Object.values(this.reportOverviews).sort((a, b) => { + const dateA = new Date(a.year, a.month - 1).getTime(); + const dateB = new Date(b.year, b.month - 1).getTime(); + if (a.year === b.year) { + // Annual reports should always be sorted before Monthly reports, + // regardless of their month + if (a.frequency === "ANNUAL") { + return -1; + } + if (b.frequency === "ANNUAL") { + return 1; + } + } + return dateB - dateA; + }); + } + + async getReportOverviews(): Promise { + try { + const { currentAgency } = this.userStore; + if (currentAgency === undefined) { + // If user is not attached to an agency, + // no need to bother trying to load reports. + runInAction(() => { + this.loadingOverview = false; + }); + } + if (currentAgency !== undefined) { + const response = (await this.api.request({ + path: `/api/agencies/${currentAgency.id}/reports`, + method: "GET", + })) as Response; + if (response.status === 200) { + const allReports = await response.json(); + + runInAction(() => { + allReports.forEach((report: ReportOverview) => { + this.reportOverviews[report.id] = report; + }); + this.loadingOverview = false; + }); + } else { + const error = await response.json(); + throw new Error(error.description); + } + } + } catch (error) { + runInAction(() => { + this.loadingOverview = false; + }); + if (error instanceof Error) return new Error(error.message); + } + } + + async getReport(reportID: number): Promise { + try { + const response = (await this.api.request({ + path: `/api/reports/${reportID}`, + method: "GET", + })) as Response; + + if (response.status !== 200) { + throw new Error("There was an issue getting this report."); + } + + const report = (await response.json()) as Report; + const { metrics, ...overview } = report; + + runInAction(() => { + this.reportOverviews[reportID] = overview; + const metricsBySystem = groupBy(metrics, (metric) => metric.system); + this.reportMetricsBySystem[reportID] = metricsBySystem; + // ensure that the order of the metrics in reportMetricsBySystem + // matches the order of the metrics in reportMetrics + this.reportMetrics[reportID] = Object.values(metricsBySystem).flat(); + }); + } catch (error) { + if (error instanceof Error) return new Error(error.message); + } + } + + async createReport( + body: Record + ): Promise { + try { + const { currentAgency } = this.userStore; + + if (currentAgency === undefined) { + throw new Error( + "Either invalid user/agency information or no user or agency information initialized." + ); + } + + const response = (await this.api.request({ + path: "/api/reports", + method: "POST", + body: { agency_id: currentAgency.id, ...body }, + })) as Response; + + return response; + } catch (error) { + if (error instanceof Error) return new Error(error.message); + } + } + + async updateReport( + reportID: number, + updatedMetrics: UpdatedMetricsValues[], + status: ReportStatus + ): Promise { + try { + const response = (await this.api.request({ + path: `/api/reports/${reportID}`, + body: { + status, + time_loaded: + this.reportOverviews[reportID].last_modified_at_timestamp, + metrics: updatedMetrics, + }, + method: "PATCH", + })) as Response; + + if (response.status === 200) { + /** Update the editor details (editors & last modified details) in real time within the report after autosave. */ + const report = (await response.json()) as Report; + this.reportOverviews[report.id] = report; + } + + return response; + } catch (error) { + if (error instanceof Error) return new Error(error.message); + } + } + + async deleteReports( + reportIDs: number[] + ): Promise { + try { + const response = (await this.api.request({ + path: `/api/reports`, + body: { report_ids: reportIDs }, + method: "DELETE", + })) as Response; + + if (response.status !== 200) { + throw new Error("There was an issue deleting these reports."); + } + + runInAction(() => { + this.resetState(); + this.getReportOverviews(); + }); + + return response; + } catch (error) { + if (error instanceof Error) return new Error(error.message); + } + } + + async getReportSettings(): Promise { + try { + const { currentAgency } = this.userStore; + + if (currentAgency === undefined) { + throw new Error( + "Either invalid user/agency information or no user or agency information initialized." + ); + } + + const response = (await this.api.request({ + path: `/api/agencies/${currentAgency.id}/metrics`, + method: "GET", + })) as Response; + + if (response.status !== 200) { + throw new Error("There was an issue retrieving the report settings."); + } + + return response; + } catch (error) { + if (error instanceof Error) return new Error(error.message); + } + } + + async updateReportSettings( + updatedMetricSettings: MetricSettings[] + ): Promise { + try { + const { currentAgency } = this.userStore; + + if (currentAgency === undefined) { + throw new Error( + "Either invalid user/agency information or no user or agency information initialized." + ); + } + + const response = (await this.api.request({ + path: `/api/agencies/${currentAgency.id}/metrics`, + body: { metrics: updatedMetricSettings }, + method: "PUT", + })) as Response; + + if (response.status !== 200) { + throw new Error("There was an issue updating the report settings."); + } + + return response; + } catch (error) { + if (error instanceof Error) return new Error(error.message); + } + } + + async getUploadedFilesList(): Promise { + const { currentAgency } = this.userStore; + + if (currentAgency === undefined) { + return new Error( + "Either invalid user/agency information or no user or agency information initialized." + ); + } + + const response = (await this.api.request({ + path: `/api/agencies/${currentAgency.id}/spreadsheets`, + method: "GET", + })) as Response; + + if (response.status !== 200) { + return new Error("There was an issue retrieving the list of files."); + } + + return response; + } + + async fetchSpreadsheetBlob( + spreadsheetID: number + ): Promise { + try { + const response = (await this.api.request({ + path: `/api/spreadsheets/${spreadsheetID}`, + method: "GET", + })) as Response; + + if (response.status !== 200) { + throw new Error("There was an issue downloading the spreadsheet."); + } + + return response; + } catch (error) { + if (error instanceof Error) return new Error(error.message); + } + } + + async uploadExcelSpreadsheet( + formData: FormData + ): Promise { + try { + const response = (await this.api.request({ + path: `/api/spreadsheets`, + body: formData, + method: "POST", + })) as Response; + + if (response.status !== 200) { + throw new Error("There was an issue uploading the file."); + } + + return response; + } catch (error) { + if (error instanceof Error) return new Error(error.message); + } + } + + async deleteUploadedSpreadsheet( + spreadsheetID: number + ): Promise { + const response = (await this.api.request({ + path: `/api/spreadsheets/${spreadsheetID}`, + method: "DELETE", + })) as Response; + + if (response.status !== 200) { + return new Error("There was an issue deleting the file."); + } + + return response; + } + + async updateFileStatus( + spreadsheetID: number, + status: UploadedFileStatus + ): Promise { + try { + const response = (await this.api.request({ + path: `/api/spreadsheets/${spreadsheetID}`, + body: { status }, + method: "PATCH", + })) as Response; + + if (response.status !== 200) { + throw new Error("There was an issue updating the file status."); + } + + return response; + } catch (error) { + if (error instanceof Error) return new Error(error.message); + } + } + + resetState() { + // reset the state when switching agencies + runInAction(() => { + this.reportOverviews = {}; + this.reportMetrics = {}; + this.reportMetricsBySystem = {}; + this.loadingOverview = true; + }); + } +} + +export default ReportStore; diff --git a/publisher/src/stores/RootStore.ts b/publisher/src/stores/RootStore.ts new file mode 100644 index 000000000..c9575fd8d --- /dev/null +++ b/publisher/src/stores/RootStore.ts @@ -0,0 +1,66 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= +import { Auth0ClientOptions } from "@auth0/auth0-spa-js"; + +import { AuthStore } from "../components/Auth"; +import API from "./API"; +import DatapointsStore from "./DatapointsStore"; +import FormStore from "./FormStore"; +import ReportStore from "./ReportStore"; +import UserStore from "./UserStore"; + +const getAuthSettings = (): Auth0ClientOptions | undefined => { + if (window.APP_CONFIG) { + return { + domain: window.APP_CONFIG.domain, + client_id: window.APP_CONFIG.clientId, + redirect_uri: window.location.origin, + audience: window.APP_CONFIG.audience, + useRefreshTokens: true, + }; + } + return undefined; +}; + +class RootStore { + authStore: AuthStore; + + api: API; + + userStore: UserStore; + + reportStore: ReportStore; + + formStore: FormStore; + + datapointsStore: DatapointsStore; + + constructor() { + this.authStore = new AuthStore({ + authSettings: getAuthSettings(), + }); + this.api = new API(this.authStore); + this.userStore = new UserStore(this.authStore, this.api); + this.reportStore = new ReportStore(this.userStore, this.api); + this.formStore = new FormStore(this.reportStore); + this.datapointsStore = new DatapointsStore(this.userStore, this.api); + } +} + +export default new RootStore(); + +export type { RootStore }; diff --git a/publisher/src/stores/StoreProvider.tsx b/publisher/src/stores/StoreProvider.tsx new file mode 100644 index 000000000..d6bd63cec --- /dev/null +++ b/publisher/src/stores/StoreProvider.tsx @@ -0,0 +1,48 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React, { useContext } from "react"; + +import NoAuthConfigErrorPage from "../components/Error/NoAuthConfigErrorPage"; +import rootStore from "./RootStore"; + +const StoreContext = React.createContext( + undefined +); + +export const StoreProvider: React.FC = ({ + children, +}): React.ReactElement => { + if (window.APP_CONFIG) { + return ( + + {children} + + ); + } + return ; +}; + +export function useStore(): typeof rootStore { + const context = useContext(StoreContext); + + if (context === undefined) { + throw new Error("useStore must be used within a StoreProvider"); + } + + return context; +} diff --git a/publisher/src/stores/UserStore.ts b/publisher/src/stores/UserStore.ts new file mode 100644 index 000000000..06c1cdb27 --- /dev/null +++ b/publisher/src/stores/UserStore.ts @@ -0,0 +1,235 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= +import { makeAutoObservable, runInAction, when } from "mobx"; +import { makePersistable } from "mobx-persist-store"; + +import { APP_METADATA_CLAIM, AuthStore } from "../components/Auth"; +import { showToast } from "../components/Toast"; +import { UserAgency } from "../shared/types"; +import API from "./API"; + +type UserSettingsRequestBody = { + name: string | null; + email: string | null; +}; +class UserStore { + authStore: AuthStore; + + api: API; + + auth0UserID: string | undefined; + + userAgencies: UserAgency[] | undefined; + + userInfoLoaded: boolean; + + onboardingTopicsCompleted: { [topic: string]: boolean } | undefined; + + permissions: string[]; + + currentAgencyId: number | undefined; + + constructor(authStore: AuthStore, api: API) { + makeAutoObservable(this); + makePersistable(this, { + name: "UserStore", + properties: ["currentAgencyId"], + storage: window.localStorage, + }); + + this.authStore = authStore; + this.api = api; + this.auth0UserID = this.authStore.user?.id; + this.userAgencies = undefined; + this.userInfoLoaded = false; + this.onboardingTopicsCompleted = undefined; + this.permissions = []; + this.currentAgencyId = undefined; + + when( + () => api.isSessionInitialized, + () => this.updateAndRetrieveUserPermissionsAndAgencies() + ); + } + + async updateUserNameAndEmail( + name: string, + email: string + ): Promise { + try { + const body: UserSettingsRequestBody = { name: null, email: null }; + const isNameUpdated = name !== this.authStore.user?.name; + const isEmailUpdated = email !== this.authStore.user?.email; + if (isNameUpdated) { + body.name = name; + } + if (isEmailUpdated) { + body.email = email; + } + const response = await this.api.request({ + path: "/api/users", + method: "PATCH", + body, + }); + runInAction(() => { + this.authStore.user = { ...this.authStore.user, name, email }; + }); + + if (response && response instanceof Response) { + if (response.status === 200 && isNameUpdated && !isEmailUpdated) { + showToast(`Name was successfully updated to ${name}.`, true); + return; + } + if (response.status === 200 && isNameUpdated && isEmailUpdated) { + showToast( + `Name and email were successfully updated. You will be logged out. Please check your email at ${email} to verify your new email before logging in again.`, + /* check */ true, + /* color */ undefined, + /* timeout */ 4500 + ); + return; + } + if (response.status === 200 && !isNameUpdated && isEmailUpdated) { + showToast( + `Email was successfully updated. You will be logged out. Please check your email at ${email} to verify your new email before logging in again.`, + /* check */ true, + /* color */ undefined, + /* timeout */ 4500 + ); + return; + } + if (response.status !== 200) { + showToast("Failed to update user details.", false, "red"); + return; + } + } + } catch (error) { + let errorMessage = ""; + if (error instanceof Error) { + errorMessage = error.message; + } else { + errorMessage = String(error); + } + + showToast(`Failed to update user details. ${errorMessage}`, false, "red"); + return errorMessage; + } + } + + getInitialAgencyId(): number | undefined { + // this.currentAgencyId is persisted in the user's localStorage. + // First, try to retrieve the persisted value + if (this.currentAgencyId !== undefined) { + const currentAgency = this.userAgencies?.find( + (agency) => agency.id === this.currentAgencyId + ); + // if the agency exists, set current agency to the persisted value + if (currentAgency) { + return this.currentAgencyId; + } + } + // if the agency does not exist, or there is no persisted currentAgencyId value, + // just set the current agency id to the first agency in the array of user agencies + if (this.userAgencies && this.userAgencies.length > 0) { + // attempting to access 0 index in the empty array leads to the mobx warning "[mobx] Out of bounds read: 0" + // so check the length of the array before accessing + return this.userAgencies[0].id; + } + return undefined; + } + + get name(): string | undefined { + return this.authStore.user?.name; + } + + get email(): string | undefined { + return this.authStore.user?.email; + } + + get nameOrEmail(): string | undefined { + return this.name || this.email; + } + + get currentAgency(): UserAgency | undefined { + return this.userAgencies?.find( + (agency) => agency.id === this.currentAgencyId + ); + } + + setCurrentAgencyId(agencyId: number | undefined) { + runInAction(() => { + this.currentAgencyId = agencyId; + }); + } + + async updateAndRetrieveUserPermissionsAndAgencies() { + try { + const response = (await this.api.request({ + path: "/api/users", + method: "PUT", + body: { + name: this.name, + }, + })) as Response; + const { agencies: userAgencies, permissions } = await response.json(); + runInAction(() => { + this.userAgencies = userAgencies; + this.permissions = permissions; + this.currentAgencyId = this.getInitialAgencyId(); + this.userInfoLoaded = true; + this.onboardingTopicsCompleted = this.authStore.user?.[ + APP_METADATA_CLAIM + ]?.onboarding_topics_completed || { + reportsview: false, + dataentryview: false, + }; + }); + } catch (error) { + if (error instanceof Error) return error.message; + return String(error); + } + } + + async updateOnboardingStatus(topic: string, status: boolean) { + try { + const response = (await this.api.request({ + path: "/api/users", + method: "PUT", + body: { + onboarding_topics_completed: { + ...this.onboardingTopicsCompleted, + [topic]: status, + }, + }, + })) as Response; + + runInAction(() => { + this.onboardingTopicsCompleted = { + ...this.onboardingTopicsCompleted, + [topic]: status, + }; + }); + + return response; + } catch (error) { + if (error instanceof Error) return error.message; + return String(error); + } + } +} + +export default UserStore; diff --git a/publisher/src/stores/index.ts b/publisher/src/stores/index.ts new file mode 100644 index 000000000..9786834b2 --- /dev/null +++ b/publisher/src/stores/index.ts @@ -0,0 +1,22 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./API"; +export * from "./ReportStore"; +export { default as rootStore } from "./RootStore"; +export * from "./StoreProvider"; +export * from "./UserStore"; diff --git a/publisher/src/utils/conversionUtils.ts b/publisher/src/utils/conversionUtils.ts new file mode 100644 index 000000000..73ec81d43 --- /dev/null +++ b/publisher/src/utils/conversionUtils.ts @@ -0,0 +1,29 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +/** + * Converts pixel to rem based on a root `font-size` of 16px. + * + * @param px - pixel value as "24px" + * @param root (optional) - change conversion from a default root `font-size` of 16px + * @returns rem value as string + */ + +export const rem = (px: string, root?: number) => { + const pxAsNumber = Number(px.replace("px", "")); + return `${pxAsNumber / (root || 16)}rem`; +}; diff --git a/publisher/src/utils/dateUtils.test.ts b/publisher/src/utils/dateUtils.test.ts new file mode 100644 index 000000000..f5b962be8 --- /dev/null +++ b/publisher/src/utils/dateUtils.test.ts @@ -0,0 +1,134 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { + printDateRangeFromMonthYear, + printElapsedDaysMonthsYearsSinceDate, +} from "./dateUtils"; + +describe("printDateRangeFromMonthYear", () => { + test("monthly", () => { + const result1 = printDateRangeFromMonthYear(1, 2022); + const result2 = printDateRangeFromMonthYear(2, 2022); + const result3 = printDateRangeFromMonthYear(3, 2022); + const result4 = printDateRangeFromMonthYear(4, 2022); + const result5 = printDateRangeFromMonthYear(5, 2022); + const result6 = printDateRangeFromMonthYear(6, 2022); + const result7 = printDateRangeFromMonthYear(7, 2022); + const result8 = printDateRangeFromMonthYear(8, 2022); + const result9 = printDateRangeFromMonthYear(9, 2022); + const result10 = printDateRangeFromMonthYear(10, 2022); + const result11 = printDateRangeFromMonthYear(11, 2022); + const result12 = printDateRangeFromMonthYear(12, 2022); + const result13 = printDateRangeFromMonthYear(1, 2020); + const result14 = printDateRangeFromMonthYear(12, 2020); + expect(result1).toEqual("January 1, 2022 - January 31, 2022"); + expect(result2).toEqual("February 1, 2022 - February 28, 2022"); + expect(result3).toEqual("March 1, 2022 - March 31, 2022"); + expect(result4).toEqual("April 1, 2022 - April 30, 2022"); + expect(result5).toEqual("May 1, 2022 - May 31, 2022"); + expect(result6).toEqual("June 1, 2022 - June 30, 2022"); + expect(result7).toEqual("July 1, 2022 - July 31, 2022"); + expect(result8).toEqual("August 1, 2022 - August 31, 2022"); + expect(result9).toEqual("September 1, 2022 - September 30, 2022"); + expect(result10).toEqual("October 1, 2022 - October 31, 2022"); + expect(result11).toEqual("November 1, 2022 - November 30, 2022"); + expect(result12).toEqual("December 1, 2022 - December 31, 2022"); + expect(result13).toEqual("January 1, 2020 - January 31, 2020"); + expect(result14).toEqual("December 1, 2020 - December 31, 2020"); + }); + + test("annual", () => { + const result1 = printDateRangeFromMonthYear(1, 2022, "ANNUAL"); + const result2 = printDateRangeFromMonthYear(7, 2022, "ANNUAL"); + expect(result1).toEqual("January 1, 2022 - December 31, 2022"); + expect(result2).toEqual("July 1, 2022 - June 30, 2023"); + }); +}); + +describe("printElapsedDaysMonthsYearsSinceDate", () => { + const dayAsMilliseconds = 86400000; + const zeroDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 0 + ).toString(); + const oneDayLapsed = new Date(Date.now() - dayAsMilliseconds * 1).toString(); + const twoDaysLapsed = new Date(Date.now() - dayAsMilliseconds * 2).toString(); + const fifteenDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 15 + ).toString(); + const fourtyDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 40 + ).toString(); + const sixtyDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 60 + ).toString(); + const hundredDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 100 + ).toString(); + const fiveHundredDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 500 + ).toString(); + const nineHundredDaysLapsed = new Date( + Date.now() - dayAsMilliseconds * 900 + ).toString(); + + test("0 days ago prints today", () => { + const zeroDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(zeroDaysLapsed); + const nonZeroDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(fifteenDaysLapsed); + expect(zeroDaysLapsedText).toEqual("today"); + expect(nonZeroDaysLapsedText).not.toEqual("today"); + }); + + test("1 day ago prints yesterday", () => { + const oneDayLapsedText = printElapsedDaysMonthsYearsSinceDate(oneDayLapsed); + expect(oneDayLapsedText).toEqual("yesterday"); + }); + + test("less than 31 days ago prints number of days lapsed", () => { + const twoDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(twoDaysLapsed); + const fifteenDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(fifteenDaysLapsed); + expect(twoDaysLapsedText).toEqual("2 days ago"); + expect(fifteenDaysLapsedText).toEqual("15 days ago"); + }); + + test("more than 30 days prints number of months lapsed", () => { + const fourtyDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(fourtyDaysLapsed); + const sixtyDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(sixtyDaysLapsed); + const hundredDaysLapsedText = + printElapsedDaysMonthsYearsSinceDate(hundredDaysLapsed); + expect(fourtyDaysLapsedText).toEqual("a month ago"); + expect(sixtyDaysLapsedText).toEqual("2 months ago"); + expect(hundredDaysLapsedText).toEqual("3 months ago"); + }); + + test("more than 365 days prints number of years lapsed", () => { + const fiveHundredDaysLapsedText = printElapsedDaysMonthsYearsSinceDate( + fiveHundredDaysLapsed + ); + const nineHundredDaysLapsedText = printElapsedDaysMonthsYearsSinceDate( + nineHundredDaysLapsed + ); + expect(fiveHundredDaysLapsedText).toEqual("a year ago"); + expect(nineHundredDaysLapsedText).toEqual("2 years ago"); + }); +}); diff --git a/publisher/src/utils/dateUtils.ts b/publisher/src/utils/dateUtils.ts new file mode 100644 index 000000000..a3ec6e09b --- /dev/null +++ b/publisher/src/utils/dateUtils.ts @@ -0,0 +1,133 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { ReportFrequency } from "../shared/types"; + +export const monthsByName = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; + +/** + * @returns the month and year as a string + * @example "March 2022" + */ +export const printDateAsMonthYear = (month: number, year: number): string => { + return new Intl.DateTimeFormat("en-US", { + month: "long", + year: "numeric", + }).format(Date.UTC(year, month, -15)); +}; + +/** + * @returns either "Annual Report [YEAR]" or "[MONTH] [YEAR]" as a string depending on frequency + * @example "Annual Report 2022" or "March 2022" + */ +export const printReportTitle = ( + month: number, + year: number, + frequency: ReportFrequency +): string => { + if (frequency === "ANNUAL") { + return `Annual Report ${year}`; + } + + return printDateAsMonthYear(month, year); +}; + +/** + * @returns elapsed number of days since a provided date as a string + * @example 'today', 'yesterday', '2 days ago', '3 months ago', '5 years ago' + */ +export const printElapsedDaysMonthsYearsSinceDate = (date: string): string => { + const now = +new Date(Date.now()); + const stringDateToNumber = +new Date(date); + const daysLapsed = Math.floor( + (now - stringDateToNumber) / (1000 * 60 * 60 * 24) + ); + + if (daysLapsed === 0) { + return `today`; + } + + if (daysLapsed === 1) { + return `yesterday`; + } + + if (daysLapsed < 31) { + return `${daysLapsed !== 1 ? daysLapsed : "a"} day${ + daysLapsed !== 1 ? "s" : "" + } ago`; + } + + if (daysLapsed > 30 && daysLapsed < 365) { + const monthsLapsed = Math.floor(daysLapsed / 30); + return `${monthsLapsed !== 1 ? monthsLapsed : "a"} month${ + monthsLapsed !== 1 ? "s" : "" + } ago`; + } + + if (daysLapsed >= 365) { + const yearsLapsed = Math.floor(daysLapsed / 365); + return `${yearsLapsed !== 1 ? yearsLapsed : "a"} year${ + yearsLapsed !== 1 ? "s" : "" + } ago`; + } + + return ""; +}; + +/** + * Prints a human-readable date range of the provided month based on month and year + * @returns date range of the month as a string + * @example printDateRangeFromMonthYear(12, 2022) returns 'December 1, 2022 - December 31, 2022' + */ +export const printDateRangeFromMonthYear = ( + month: number, + year: number, + frequency: ReportFrequency = "MONTHLY" +): string => { + /** + * Note: backend sends true month number, whereas JavaScript's Date API deals with zero-indexed month numbers + * The below method of calculating the last day (number) of a given month relies on getting the 0th day of the following month. + * Simply providing the true month number value (from `month` param) does the + 1 (following month) calculation for us. + */ + + if (frequency === "MONTHLY") { + const lastDayOfMonth = new Date(year, month, 0)?.getDate(); + const currentMonth = monthsByName[month - 1]; + return `${currentMonth} 1, ${year} - ${currentMonth} ${lastDayOfMonth}, ${year}`; + } + + const currentMonth = monthsByName[month - 1]; + const prevMonthNumber = month === 1 ? 12 : month - 1; + const prevMonth = monthsByName[prevMonthNumber - 1]; + const lastDayOfPrevMonth = new Date(year, prevMonthNumber, 0)?.getDate(); + return `${currentMonth} 1, ${year} - ${prevMonth} ${lastDayOfPrevMonth}, ${ + month === 1 ? year : year + 1 + }`; +}; diff --git a/publisher/src/utils/helperUtils.test.ts b/publisher/src/utils/helperUtils.test.ts new file mode 100644 index 000000000..c07454fa6 --- /dev/null +++ b/publisher/src/utils/helperUtils.test.ts @@ -0,0 +1,127 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { + formatNumberInput, + isPositiveNumber, + normalizeToString, + sanitizeInputValue, +} from "./helperUtils"; + +describe("sanitizeInputValue", () => { + test("return previous value if input value is undefined", () => { + const undefinedInput = sanitizeInputValue(undefined, 2); + const definedInput = sanitizeInputValue("1", 2); + + expect(undefinedInput).toBe(2); + expect(definedInput).toBe(1); + }); + + test("return null if empty string input", () => { + const emptyStringInput = sanitizeInputValue("", 2); + const nonEmptyStringInput = sanitizeInputValue("text", 2); + + expect(emptyStringInput).toBeNull(); + expect(nonEmptyStringInput).not.toBeNull(); + }); + + test("return the number zero for string 0 and 0.00 with decimals", () => { + const zeroString = sanitizeInputValue("0", null); + const zeroDecimal = sanitizeInputValue("0.00", null); + + expect(zeroString).toBe(0); + expect(zeroDecimal).toBe(0); + }); + + test("return value converted to number if convertible", () => { + const numberString = sanitizeInputValue("123", null); + const numberStringWithDecimals = sanitizeInputValue("123.2341", null); + const numberStringWithDecimalsAfterZero = sanitizeInputValue( + "0.12341", + null + ); + + expect(numberString).toBe(123); + expect(numberStringWithDecimals).toBe(123.2341); + expect(numberStringWithDecimalsAfterZero).toBe(0.12341); + }); + + test("return value as string if not convertible to number", () => { + const nonNumber = sanitizeInputValue("0.123abc", null); + expect(typeof nonNumber).toBe("string"); + }); +}); + +describe("normalizeToString", () => { + test("return string version of value", () => { + const undefinedInput = normalizeToString(undefined); + const nullInput = normalizeToString(null); + const booleanInput = normalizeToString(false); + const numberInput = normalizeToString(22); + const stringInput = normalizeToString("Hello"); + + expect(undefinedInput).toBe(""); + expect(nullInput).toBe(""); + expect(booleanInput).toBe("false"); + expect(numberInput).toBe("22"); + expect(stringInput).toBe("Hello"); + }); +}); + +describe("formatNumberInput", () => { + test("return formatted number with commas and decimals", () => { + const inputWithCommasSpaces = formatNumberInput( + " 1231223,23,23,3,3.123123123 11 " + ); + const inputWithSeriesOfNumbers = formatNumberInput("123122323233312"); + + expect(inputWithCommasSpaces).toBe("1,231,223,232,333.12312312311"); + expect(inputWithSeriesOfNumbers).toBe("123,122,323,233,312"); + }); + + test("return formatted number on first decimal instance", () => { + const inputWithDecimalAtEnd = formatNumberInput("2,32,3,23,2."); + expect(inputWithDecimalAtEnd).toBe("2,323,232."); + }); + + test("return input value if not valid", () => { + const invalidInput = formatNumberInput("12xyz!"); + expect(invalidInput).toBe("12xyz!"); + }); +}); + +describe("isPositiveNumber", () => { + test("valid positive numbers return true", () => { + expect(isPositiveNumber("1")).toBe(true); + expect(isPositiveNumber("12")).toBe(true); + expect(isPositiveNumber("13")).toBe(true); + expect(isPositiveNumber("3.4")).toBe(true); + expect(isPositiveNumber("0")).toBe(true); + }); + test("negative numbers return false", () => { + expect(isPositiveNumber("-1")).toBe(false); + expect(isPositiveNumber("-5")).toBe(false); + expect(isPositiveNumber("-5.6")).toBe(false); + }); + test("invalid numbers return false", () => { + expect(isPositiveNumber("-1 ")).toBe(false); + expect(isPositiveNumber("0.0.0")).toBe(false); + expect(isPositiveNumber("five")).toBe(false); + expect(isPositiveNumber(" ")).toBe(false); + expect(isPositiveNumber("")).toBe(false); + }); +}); diff --git a/publisher/src/utils/helperUtils.ts b/publisher/src/utils/helperUtils.ts new file mode 100644 index 000000000..94036fb50 --- /dev/null +++ b/publisher/src/utils/helperUtils.ts @@ -0,0 +1,222 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { debounce, memoize } from "lodash"; + +import { MetricContext } from "../shared/types"; + +export const isPositiveNumber = (value: string) => { + return (value.trim() !== "" && Number(value) === 0) || Number(value) > 0; +}; + +/** + * Separate multiple people on a list by comma - no comma for the last person on the list + * @example ['Editor 1', 'Editor 2', 'Editor 3'] would print: `Editor 1, Editor 2, Editor 3` + */ +export const printCommaSeparatedList = (list: string[]): string => { + const string = list.map((item, i) => + i < list.length - 1 ? `${item}, ` : `${item}` + ); + return string.join(" "); +}; + +/** + * Take a string, trim and remove all spacing, and lowercase it. + * @example normalizeString("All Reports ") will be "allreports" + */ +export const normalizeString = (string: string): string => { + return string.split(" ").join("").toLowerCase().trim(); +}; + +/** + * Take a string, replace _ with ' ' space. + * @example "NOT_STARTED" becomes "NOT STARTED" + */ +export const removeSnakeCase = (string: string): string => { + return string.split("_").join(" "); +}; + +/** + * Concatenate two string keys by an `_` underscore (default) or a specified separator string + * @returns a single concatenated string + * @examples + * combineTwoKeyNames("KEY1", "KEY2") will return "KEY1_KEY2" + * combineTwoKeyNames("KEY1", "KEY2", "-") will return "KEY1-KEY2" + */ +export const combineTwoKeyNames = ( + key1: string, + key2: string, + separator?: string +) => { + return `${key1}${separator || "_"}${key2}`; +}; + +/** + * Remove commas, spaces and trim string + * + * @returns a trimmed string free from spaces and commas + * @example " 1,000,00 0 " becomes "1000000" + */ + +export const removeCommaSpaceAndTrim = (string: string) => { + return string?.replaceAll(",", "").replaceAll(" ", "").trim(); +}; + +/** + * Formats string version of numbers into string format with thousands separator + * + * @returns a string representation of a number with commas + * @example " 1231223,23,23,3,3.123123123 11 " " becomes "1,231,223,232,333.12312312311" + */ + +export const formatNumberInput = ( + value: string | undefined +): string | undefined => { + if (value === undefined) { + return undefined; + } + + const maxNumber = 999_999_999_999_999; // 1 quadrillion + const cleanValue = removeCommaSpaceAndTrim(value); + const splitValues = cleanValue.split("."); + + if (Number(cleanValue) > maxNumber) { + return Number(cleanValue.slice(0, 15)).toLocaleString(); + } + + if (splitValues && splitValues.length === 2) { + if (cleanValue[cleanValue.length - 1] === ".") { + return Number(splitValues[0]) !== 0 && Number(splitValues[0]) + ? `${Number(splitValues[0]).toLocaleString()}.` + : value; + } + + if (cleanValue.includes(".")) { + const [wholeNumber, decimal] = cleanValue.split("."); + return Number(wholeNumber) + ? `${Number(wholeNumber).toLocaleString()}.${decimal}` + : value; + } + } + return Number(cleanValue) ? Number(cleanValue).toLocaleString() : value; +}; + +/** + * Sanitize by formatting and converting string input to appropriate value for backend. + * + * @param value input value + * @param previousValue previously saved value retrieved from the backend + * @returns + * * `previousValue` from the backend if `value` is undefined + * * `null` for empty string + * * number `0` for true zeros ("0", "0.000", etc.) + * * `value` converted to number + * * `value` itself (if it is not a number) or if the type is "TEXT" + */ + +export const sanitizeInputValue = ( + value: string | undefined, + previousValue: string | number | boolean | null | undefined, + type?: MetricContext["type"] +): string | number | boolean | null | undefined => { + if (value === undefined) { + return previousValue; + } + const cleanValue = removeCommaSpaceAndTrim(value); + if (cleanValue === "") { + return null; + } + if (type === "TEXT") { + return value; + } + if (Number(cleanValue) === 0) { + return 0; + } + return Number(cleanValue) || value; +}; + +/** + * Converts string | number | boolean | null | undefined into string equivalents that conforms to a text input + * + * @returns a string, "" empty string to represent null and undefined, stringified version of number and boolean + */ +export const normalizeToString = ( + value: string | number | boolean | null | undefined +): string => { + const stringValue = value?.toString(); + return !stringValue ? "" : stringValue; +}; + +/** + * Group a list of objects based on property value + * @param arr list of objects + * @param key name of the property on which to perform the grouping + * @returns dictionary of property value to list of objects with that value + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const groupBy = (arr: T[], key: (i: T) => K) => { + const result = {} as Record; + arr.forEach((item) => { + if (!result[key(item)]) { + result[key(item)] = []; + } + result[key(item)].push(item); + }); + return result; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface MemoizeDebouncedFunction any> { + (...args: Parameters): void; + flush: (...args: Parameters) => void; +} + +/** + * This method should be used instead of the standard `debounce` if we want to + * debounce *only* if the arguments to the function are the same. + * For instance, consider a function `click(param: str)`. With standard debounce, + * calling `click('foo')` and `click('bar')` in quick succession will only result + * in the execution of `click('bar')`. However, using memoized debounce, both + * functions will execute, because their parameters are different. + * Taken from https://github.com/lodash/lodash/issues/2403#issuecomment-816137402 + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function memoizeDebounce any>( + func: F, + wait = 0, + options: _.DebounceSettings = {}, + resolver?: (...args: Parameters) => unknown +): MemoizeDebouncedFunction { + const debounceMemo = memoize<(...args: Parameters) => _.DebouncedFunc>( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (..._args: Parameters) => debounce(func, wait, options), + resolver + ); + + function wrappedFunction( + this: MemoizeDebouncedFunction, + ...args: Parameters + ): ReturnType | undefined { + return debounceMemo(...args)(...args); + } + + wrappedFunction.flush = (...args: Parameters): void => { + debounceMemo(...args).flush(); + }; + + return wrappedFunction as unknown as MemoizeDebouncedFunction; +} diff --git a/publisher/src/utils/index.ts b/publisher/src/utils/index.ts new file mode 100644 index 000000000..3d8962b73 --- /dev/null +++ b/publisher/src/utils/index.ts @@ -0,0 +1,20 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export * from "./conversionUtils"; +export * from "./dateUtils"; +export * from "./helperUtils"; diff --git a/publisher/src/window.d.ts b/publisher/src/window.d.ts new file mode 100644 index 000000000..f89db5ce8 --- /dev/null +++ b/publisher/src/window.d.ts @@ -0,0 +1,32 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2022 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +// According to https://github.com/microsoft/TypeScript/issues/33128#issuecomment-748937504, +// this line is needed in order to turn this from a script into a module in order +// to allow the interface definition to be extended. +export {}; + +declare global { + interface Window { + // values added from `app_public_config.js` + APP_CONFIG: Record; + SEGMENT_KEY: string; + + // Segment's analytics.js + analytics: SegmentAnalytics.AnalyticsJS; + } +} diff --git a/publisher/tsconfig.json b/publisher/tsconfig.json new file mode 100644 index 000000000..c37853f2e --- /dev/null +++ b/publisher/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "@recidiviz/tsconfig/react" +} diff --git a/publisher/yarn.lock b/publisher/yarn.lock new file mode 100644 index 000000000..56db0afb2 --- /dev/null +++ b/publisher/yarn.lock @@ -0,0 +1,9709 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.1.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" + integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== + dependencies: + "@jridgewell/trace-mapping" "^0.3.0" + +"@apideck/better-ajv-errors@^0.3.1": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.3.tgz#ab0b1e981e1749bf59736cf7ebe25cfc9f949c15" + integrity sha512-9o+HO2MbJhJHjDYZaDxJmSDckvDpiuItEsrIShV0DXeCshXWRHhqYyU/PKHMkuClOmFnZhRd6wzv4vpDu/dRKg== + dependencies: + json-schema "^0.4.0" + jsonpointer "^5.0.0" + leven "^3.1.0" + +"@auth0/auth0-react@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@auth0/auth0-react/-/auth0-react-1.9.0.tgz#957041fa9756a5cf20412116f19f2d1367aa66b6" + integrity sha512-VkovI2i7dI1dkIJAdS51O9XMq6vlcHJn9DL+if4rSxt+sNocFd1i4q0j07a9XbafBj4/WEkSbOiX93z2ZXqdAA== + dependencies: + "@auth0/auth0-spa-js" "^1.19.3" + +"@auth0/auth0-spa-js@^1.19.3", "@auth0/auth0-spa-js@^1.20.1": + version "1.20.1" + resolved "https://registry.yarnpkg.com/@auth0/auth0-spa-js/-/auth0-spa-js-1.20.1.tgz#8e49e76f0a8f5153595489a1399c980738609a65" + integrity sha512-hJyZ1n/7K0srArsnR9RKViGKh550SBE7nNB4g0F0X9H3VWCbETlCDWgnRckrhy045Bmb10VVcDBes3uImWTnjw== + dependencies: + abortcontroller-polyfill "^1.7.3" + browser-tabs-lock "^1.2.15" + core-js "^3.20.3" + es-cookie "^1.3.2" + fast-text-encoding "^1.0.3" + promise-polyfill "^8.2.1" + unfetch "^4.2.0" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.8", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2" + integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ== + +"@babel/core@^7.0.0-0", "@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.7.2", "@babel/core@^7.8.0": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.7.tgz#f7c28228c83cdf2dbd1b9baa06eaf9df07f0c2f9" + integrity sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.7" + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helpers" "^7.17.7" + "@babel/parser" "^7.17.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + +"@babel/eslint-parser@^7.16.3": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.17.0.tgz#eabb24ad9f0afa80e5849f8240d0e5facc2d90d6" + integrity sha512-PUEJ7ZBXbRkbq3qqM/jZ2nIuakUBqCYc7Qf52Lj7dlZ6zERnqisdHioL0l4wwQZnmskMeasqUNzLBFKs3nylXA== + dependencies: + eslint-scope "^5.1.1" + eslint-visitor-keys "^2.1.0" + semver "^6.3.0" + +"@babel/generator@^7.17.3", "@babel/generator@^7.17.7", "@babel/generator@^7.7.2": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" + integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w== + dependencies: + "@babel/types" "^7.17.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" + integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b" + integrity sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46" + integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-validator-option" "^7.16.7" + browserslist "^4.17.5" + semver "^6.3.0" + +"@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.1", "@babel/helper-create-class-features-plugin@^7.17.6": + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz#3778c1ed09a7f3e65e6d6e0f6fbfcc53809d92c9" + integrity sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + +"@babel/helper-create-regexp-features-plugin@^7.16.7": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz#1dcc7d40ba0c6b6b25618997c5dbfd310f186fe1" + integrity sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + regexpu-core "^5.0.1" + +"@babel/helper-define-polyfill-provider@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665" + integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA== + dependencies: + "@babel/helper-compilation-targets" "^7.13.0" + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/traverse" "^7.13.0" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-explode-assignable-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a" + integrity sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" + integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== + dependencies: + "@babel/helper-get-function-arity" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helper-get-function-arity@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" + integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-member-expression-to-functions@^7.16.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz#a34013b57d8542a8c4ff8ba3f747c02452a4d8c4" + integrity sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw== + dependencies: + "@babel/types" "^7.17.0" + +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz#3943c7f777139e7954a5355c815263741a9c1cbd" + integrity sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" + +"@babel/helper-optimise-call-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2" + integrity sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" + integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== + +"@babel/helper-remap-async-to-generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" + integrity sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-wrap-function" "^7.16.8" + "@babel/types" "^7.16.8" + +"@babel/helper-replace-supers@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz#e9f5f5f32ac90429c1a4bdec0f231ef0c2838ab1" + integrity sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helper-simple-access@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367" + integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA== + dependencies: + "@babel/types" "^7.17.0" + +"@babel/helper-skip-transparent-expression-wrappers@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09" + integrity sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/helper-validator-option@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== + +"@babel/helper-wrap-function@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz#58afda087c4cd235de92f7ceedebca2c41274200" + integrity sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw== + dependencies: + "@babel/helper-function-name" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.8" + "@babel/types" "^7.16.8" + +"@babel/helpers@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.7.tgz#6fc0a24280fd00026e85424bbfed4650e76d7127" + integrity sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w== + dependencies: + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" + +"@babel/highlight@^7.16.7": + version "7.16.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" + integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.17.7", "@babel/parser@^7.7.0": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.7.tgz#fc19b645a5456c8d6fdb6cecd3c66c0173902800" + integrity sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA== + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" + integrity sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz#cc001234dfc139ac45f6bcf801866198c8c72ff9" + integrity sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" + "@babel/plugin-proposal-optional-chaining" "^7.16.7" + +"@babel/plugin-proposal-async-generator-functions@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz#3bdd1ebbe620804ea9416706cd67d60787504bc8" + integrity sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-remap-async-to-generator" "^7.16.8" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-proposal-class-properties@^7.16.0", "@babel/plugin-proposal-class-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz#925cad7b3b1a2fcea7e59ecc8eb5954f961f91b0" + integrity sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-proposal-class-static-block@^7.16.7": + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz#164e8fd25f0d80fa48c5a4d1438a6629325ad83c" + integrity sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.17.6" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-proposal-decorators@^7.16.4": + version "7.17.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.17.2.tgz#c36372ddfe0360cac1ee331a238310bddca11493" + integrity sha512-WH8Z95CwTq/W8rFbMqb9p3hicpt4RX4f0K659ax2VHxgOyT6qQmUaEVEjIh4WR9Eh9NymkVn5vwsrE68fAQNUw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.17.1" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/plugin-syntax-decorators" "^7.17.0" + charcodes "^0.2.0" + +"@babel/plugin-proposal-dynamic-import@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz#c19c897eaa46b27634a00fee9fb7d829158704b2" + integrity sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-proposal-export-namespace-from@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz#09de09df18445a5786a305681423ae63507a6163" + integrity sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-proposal-json-strings@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz#9732cb1d17d9a2626a08c5be25186c195b6fa6e8" + integrity sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-proposal-logical-assignment-operators@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz#be23c0ba74deec1922e639832904be0bea73cdea" + integrity sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz#141fc20b6857e59459d430c850a0011e36561d99" + integrity sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-proposal-numeric-separator@^7.16.0", "@babel/plugin-proposal-numeric-separator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz#d6b69f4af63fb38b6ca2558442a7fb191236eba9" + integrity sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-object-rest-spread@^7.16.7": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz#d9eb649a54628a51701aef7e0ea3d17e2b9dd390" + integrity sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw== + dependencies: + "@babel/compat-data" "^7.17.0" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.16.7" + +"@babel/plugin-proposal-optional-catch-binding@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz#c623a430674ffc4ab732fd0a0ae7722b67cb74cf" + integrity sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@^7.16.0", "@babel/plugin-proposal-optional-chaining@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz#7cd629564724816c0e8a969535551f943c64c39a" + integrity sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-proposal-private-methods@^7.16.0", "@babel/plugin-proposal-private-methods@^7.16.11": + version "7.16.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz#e8df108288555ff259f4527dbe84813aac3a1c50" + integrity sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.16.10" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-proposal-private-property-in-object@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz#b0b8cef543c2c3d57e59e2c611994861d46a3fce" + integrity sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-proposal-unicode-property-regex@^7.16.7", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz#635d18eb10c6214210ffc5ff4932552de08188a2" + integrity sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-decorators@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.17.0.tgz#a2be3b2c9fe7d78bd4994e790896bc411e2f166d" + integrity sha512-qWe85yCXsvDEluNP0OyeQjH63DlhAR3W7K9BxxU1MvbDb48tgBG+Ao6IJJ6smPDrrVzSQZrbF6donpkFBMcs3A== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-flow@^7.14.5", "@babel/plugin-syntax-flow@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.16.7.tgz#202b147e5892b8452bbb0bb269c7ed2539ab8832" + integrity sha512-UDo3YGQO0jH6ytzVwgSLv9i/CzMcUjbKenL67dTrAZPPv6GFAtDhe6jqnvmoKzC/7htNTohhos+onPtDMqJwaQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" + integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.16.7", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8" + integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-arrow-functions@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz#44125e653d94b98db76369de9c396dc14bef4154" + integrity sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-async-to-generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz#b83dff4b970cf41f1b819f8b49cc0cfbaa53a808" + integrity sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-remap-async-to-generator" "^7.16.8" + +"@babel/plugin-transform-block-scoped-functions@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz#4d0d57d9632ef6062cdf354bb717102ee042a620" + integrity sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-block-scoping@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz#f50664ab99ddeaee5bc681b8f3a6ea9d72ab4f87" + integrity sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-classes@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz#8f4b9562850cd973de3b498f1218796eb181ce00" + integrity sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz#66dee12e46f61d2aae7a73710f591eb3df616470" + integrity sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-destructuring@^7.16.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz#49dc2675a7afa9a5e4c6bdee636061136c3408d1" + integrity sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-dotall-regex@^7.16.7", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz#6b2d67686fab15fb6a7fd4bd895d5982cfc81241" + integrity sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-duplicate-keys@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz#2207e9ca8f82a0d36a5a67b6536e7ef8b08823c9" + integrity sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-exponentiation-operator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz#efa9862ef97e9e9e5f653f6ddc7b665e8536fe9b" + integrity sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-flow-strip-types@^7.16.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.16.7.tgz#291fb140c78dabbf87f2427e7c7c332b126964b8" + integrity sha512-mzmCq3cNsDpZZu9FADYYyfZJIOrSONmHcop2XEKPdBNMa4PDC4eEvcOvzZaCNcjKu72v0XQlA5y1g58aLRXdYg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-flow" "^7.16.7" + +"@babel/plugin-transform-for-of@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz#649d639d4617dff502a9a158c479b3b556728d8c" + integrity sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz#5ab34375c64d61d083d7d2f05c38d90b97ec65cf" + integrity sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA== + dependencies: + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz#254c9618c5ff749e87cb0c0cef1a0a050c0bdab1" + integrity sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-member-expression-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz#6e5dcf906ef8a098e630149d14c867dd28f92384" + integrity sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-modules-amd@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz#b28d323016a7daaae8609781d1f8c9da42b13186" + integrity sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g== + dependencies: + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-commonjs@^7.16.8": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.7.tgz#d86b217c8e45bb5f2dbc11eefc8eab62cf980d19" + integrity sha512-ITPmR2V7MqioMJyrxUo2onHNC3e+MvfFiFIR0RP21d3PtlVb6sfzoxNKiphSZUOM9hEIdzCcZe83ieX3yoqjUA== + dependencies: + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-systemjs@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz#887cefaef88e684d29558c2b13ee0563e287c2d7" + integrity sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw== + dependencies: + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-umd@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz#23dad479fa585283dbd22215bff12719171e7618" + integrity sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ== + dependencies: + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz#7f860e0e40d844a02c9dcf9d84965e7dfd666252" + integrity sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + +"@babel/plugin-transform-new-target@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz#9967d89a5c243818e0800fdad89db22c5f514244" + integrity sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-object-super@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz#ac359cf8d32cf4354d27a46867999490b6c32a94" + integrity sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + +"@babel/plugin-transform-parameters@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz#a1721f55b99b736511cb7e0152f61f17688f331f" + integrity sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-property-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz#2dadac85155436f22c696c4827730e0fe1057a55" + integrity sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-react-constant-elements@^7.12.1": + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.17.6.tgz#6cc273c2f612a6a50cb657e63ee1303e5e68d10a" + integrity sha512-OBv9VkyyKtsHZiHLoSfCn+h6yU7YKX8nrs32xUmOa1SRSk+t03FosB6fBZ0Yz4BpD1WV7l73Nsad+2Tz7APpqw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-react-display-name@^7.16.0", "@babel/plugin-transform-react-display-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz#7b6d40d232f4c0f550ea348593db3b21e2404340" + integrity sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-react-jsx-development@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz#43a00724a3ed2557ed3f276a01a929e6686ac7b8" + integrity sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.16.7" + +"@babel/plugin-transform-react-jsx@^7.14.9", "@babel/plugin-transform-react-jsx@^7.16.7": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz#eac1565da176ccb1a715dae0b4609858808008c1" + integrity sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-jsx" "^7.16.7" + "@babel/types" "^7.17.0" + +"@babel/plugin-transform-react-pure-annotations@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz#232bfd2f12eb551d6d7d01d13fe3f86b45eb9c67" + integrity sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-regenerator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz#9e7576dc476cb89ccc5096fff7af659243b4adeb" + integrity sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz#1d798e078f7c5958eec952059c460b220a63f586" + integrity sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-runtime@^7.16.4": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz#0a2e08b5e2b2d95c4b1d3b3371a2180617455b70" + integrity sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + babel-plugin-polyfill-corejs2 "^0.3.0" + babel-plugin-polyfill-corejs3 "^0.5.0" + babel-plugin-polyfill-regenerator "^0.3.0" + semver "^6.3.0" + +"@babel/plugin-transform-shorthand-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz#e8549ae4afcf8382f711794c0c7b6b934c5fbd2a" + integrity sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-spread@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz#a303e2122f9f12e0105daeedd0f30fb197d8ff44" + integrity sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" + +"@babel/plugin-transform-sticky-regex@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz#c84741d4f4a38072b9a1e2e3fd56d359552e8660" + integrity sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-template-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz#f3d1c45d28967c8e80f53666fc9c3e50618217ab" + integrity sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-typeof-symbol@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz#9cdbe622582c21368bd482b660ba87d5545d4f7e" + integrity sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-typescript@^7.16.7": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz#591ce9b6b83504903fa9dd3652c357c2ba7a1ee0" + integrity sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-typescript" "^7.16.7" + +"@babel/plugin-transform-unicode-escapes@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz#da8717de7b3287a2c6d659750c964f302b31ece3" + integrity sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-unicode-regex@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz#0f7aa4a501198976e25e82702574c34cfebe9ef2" + integrity sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.16.4": + version "7.16.11" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.11.tgz#5dd88fd885fae36f88fd7c8342475c9f0abe2982" + integrity sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g== + dependencies: + "@babel/compat-data" "^7.16.8" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.16.7" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.16.7" + "@babel/plugin-proposal-async-generator-functions" "^7.16.8" + "@babel/plugin-proposal-class-properties" "^7.16.7" + "@babel/plugin-proposal-class-static-block" "^7.16.7" + "@babel/plugin-proposal-dynamic-import" "^7.16.7" + "@babel/plugin-proposal-export-namespace-from" "^7.16.7" + "@babel/plugin-proposal-json-strings" "^7.16.7" + "@babel/plugin-proposal-logical-assignment-operators" "^7.16.7" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.7" + "@babel/plugin-proposal-numeric-separator" "^7.16.7" + "@babel/plugin-proposal-object-rest-spread" "^7.16.7" + "@babel/plugin-proposal-optional-catch-binding" "^7.16.7" + "@babel/plugin-proposal-optional-chaining" "^7.16.7" + "@babel/plugin-proposal-private-methods" "^7.16.11" + "@babel/plugin-proposal-private-property-in-object" "^7.16.7" + "@babel/plugin-proposal-unicode-property-regex" "^7.16.7" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.16.7" + "@babel/plugin-transform-async-to-generator" "^7.16.8" + "@babel/plugin-transform-block-scoped-functions" "^7.16.7" + "@babel/plugin-transform-block-scoping" "^7.16.7" + "@babel/plugin-transform-classes" "^7.16.7" + "@babel/plugin-transform-computed-properties" "^7.16.7" + "@babel/plugin-transform-destructuring" "^7.16.7" + "@babel/plugin-transform-dotall-regex" "^7.16.7" + "@babel/plugin-transform-duplicate-keys" "^7.16.7" + "@babel/plugin-transform-exponentiation-operator" "^7.16.7" + "@babel/plugin-transform-for-of" "^7.16.7" + "@babel/plugin-transform-function-name" "^7.16.7" + "@babel/plugin-transform-literals" "^7.16.7" + "@babel/plugin-transform-member-expression-literals" "^7.16.7" + "@babel/plugin-transform-modules-amd" "^7.16.7" + "@babel/plugin-transform-modules-commonjs" "^7.16.8" + "@babel/plugin-transform-modules-systemjs" "^7.16.7" + "@babel/plugin-transform-modules-umd" "^7.16.7" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.16.8" + "@babel/plugin-transform-new-target" "^7.16.7" + "@babel/plugin-transform-object-super" "^7.16.7" + "@babel/plugin-transform-parameters" "^7.16.7" + "@babel/plugin-transform-property-literals" "^7.16.7" + "@babel/plugin-transform-regenerator" "^7.16.7" + "@babel/plugin-transform-reserved-words" "^7.16.7" + "@babel/plugin-transform-shorthand-properties" "^7.16.7" + "@babel/plugin-transform-spread" "^7.16.7" + "@babel/plugin-transform-sticky-regex" "^7.16.7" + "@babel/plugin-transform-template-literals" "^7.16.7" + "@babel/plugin-transform-typeof-symbol" "^7.16.7" + "@babel/plugin-transform-unicode-escapes" "^7.16.7" + "@babel/plugin-transform-unicode-regex" "^7.16.7" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.16.8" + babel-plugin-polyfill-corejs2 "^0.3.0" + babel-plugin-polyfill-corejs3 "^0.5.0" + babel-plugin-polyfill-regenerator "^0.3.0" + core-js-compat "^3.20.2" + semver "^6.3.0" + +"@babel/preset-modules@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" + integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-react@^7.12.5", "@babel/preset-react@^7.16.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.16.7.tgz#4c18150491edc69c183ff818f9f2aecbe5d93852" + integrity sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-transform-react-display-name" "^7.16.7" + "@babel/plugin-transform-react-jsx" "^7.16.7" + "@babel/plugin-transform-react-jsx-development" "^7.16.7" + "@babel/plugin-transform-react-pure-annotations" "^7.16.7" + +"@babel/preset-typescript@^7.16.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz#ab114d68bb2020afc069cd51b37ff98a046a70b9" + integrity sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-transform-typescript" "^7.16.7" + +"@babel/runtime-corejs3@^7.10.2": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.7.tgz#cf914f474c490ef1aa8661d47adaa0a993636e7e" + integrity sha512-TvliGJjhxis5m7xIMvlXH/xG8Oa/LK0SCUCyfKD6nLi42n5fB4WibDJ0g9trmmBB6hwpMNx+Lzbxy9/4gpMaVw== + dependencies: + core-js-pure "^3.20.2" + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.7.tgz#a5f3328dc41ff39d803f311cfe17703418cf9825" + integrity sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.1.2": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" + integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.7.6": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" + integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.16.7", "@babel/template@^7.3.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.3", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" + integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.3" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.3" + "@babel/types" "^7.17.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" + integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@csstools/normalize.css@*": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4" + integrity sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg== + +"@csstools/postcss-color-function@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-1.0.3.tgz#251c961a852c99e9aabdbbdbefd50e9a96e8a9ff" + integrity sha512-J26I69pT2B3MYiLY/uzCGKVJyMYVg9TCpXkWsRlt+Yfq+nELUEm72QXIMYXs4xA9cJA4Oqs2EylrfokKl3mJEQ== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^1.1.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-font-format-keywords@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.0.tgz#7e7df948a83a0dfb7eb150a96e2390ac642356a1" + integrity sha512-oO0cZt8do8FdVBX8INftvIA4lUrKUSCcWUf9IwH9IPWOgKT22oAZFXeHLoDK7nhB2SmkNycp5brxfNMRLIhd6Q== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-hwb-function@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.0.tgz#d6785c1c5ba8152d1d392c66f3a6a446c6034f6d" + integrity sha512-VSTd7hGjmde4rTj1rR30sokY3ONJph1reCBTUXqeW1fKwETPy1x4t/XIeaaqbMbC5Xg4SM/lyXZ2S8NELT2TaA== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-ic-unit@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.0.tgz#f484db59fc94f35a21b6d680d23b0ec69b286b7f" + integrity sha512-i4yps1mBp2ijrx7E96RXrQXQQHm6F4ym1TOD0D69/sjDjZvQ22tqiEvaNw7pFZTUO5b9vWRHzbHzP9+UKuw+bA== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^1.1.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-is-pseudo-class@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.1.tgz#472fff2cf434bdf832f7145b2a5491587e790c9e" + integrity sha512-Og5RrTzwFhrKoA79c3MLkfrIBYmwuf/X83s+JQtz/Dkk/MpsaKtqHV1OOzYkogQ+tj3oYp5Mq39XotBXNqVc3Q== + dependencies: + postcss-selector-parser "^6.0.9" + +"@csstools/postcss-normalize-display-values@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.0.tgz#ce698f688c28517447aedf15a9037987e3d2dc97" + integrity sha512-bX+nx5V8XTJEmGtpWTO6kywdS725t71YSLlxWt78XoHUbELWgoCXeOFymRJmL3SU1TLlKSIi7v52EWqe60vJTQ== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-oklab-function@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.0.2.tgz#87cd646e9450347a5721e405b4f7cc35157b7866" + integrity sha512-QwhWesEkMlp4narAwUi6pgc6kcooh8cC7zfxa9LSQNYXqzcdNUtNBzbGc5nuyAVreb7uf5Ox4qH1vYT3GA1wOg== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^1.1.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-progressive-custom-properties@^1.1.0", "@csstools/postcss-progressive-custom-properties@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz#542292558384361776b45c85226b9a3a34f276fa" + integrity sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA== + dependencies: + postcss-value-parser "^4.2.0" + +"@emotion/cache@^10.0.27": + version "10.0.29" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" + integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ== + dependencies: + "@emotion/sheet" "0.9.4" + "@emotion/stylis" "0.8.5" + "@emotion/utils" "0.11.3" + "@emotion/weak-memoize" "0.2.5" + +"@emotion/core@^10.0.14": + version "10.3.1" + resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.3.1.tgz#4021b6d8b33b3304d48b0bb478485e7d7421c69d" + integrity sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww== + dependencies: + "@babel/runtime" "^7.5.5" + "@emotion/cache" "^10.0.27" + "@emotion/css" "^10.0.27" + "@emotion/serialize" "^0.11.15" + "@emotion/sheet" "0.9.4" + "@emotion/utils" "0.11.3" + +"@emotion/css@^10.0.27": + version "10.0.27" + resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c" + integrity sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw== + dependencies: + "@emotion/serialize" "^0.11.15" + "@emotion/utils" "0.11.3" + babel-plugin-emotion "^10.0.27" + +"@emotion/hash@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" + integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== + +"@emotion/is-prop-valid@^0.8.8": + version "0.8.8" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" + integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== + dependencies: + "@emotion/memoize" "0.7.4" + +"@emotion/memoize@0.7.4": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" + integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== + +"@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16": + version "0.11.16" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad" + integrity sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg== + dependencies: + "@emotion/hash" "0.8.0" + "@emotion/memoize" "0.7.4" + "@emotion/unitless" "0.7.5" + "@emotion/utils" "0.11.3" + csstype "^2.5.7" + +"@emotion/sheet@0.9.4": + version "0.9.4" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5" + integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== + +"@emotion/stylis@0.8.5", "@emotion/stylis@^0.8.4": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" + integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== + +"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + +"@emotion/utils@0.11.3": + version "0.11.3" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924" + integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw== + +"@emotion/weak-memoize@0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" + integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== + +"@eslint/eslintrc@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" + integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.3.1" + globals "^13.9.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@fontsource/libre-baskerville@^4.1.0": + version "4.5.3" + resolved "https://registry.yarnpkg.com/@fontsource/libre-baskerville/-/libre-baskerville-4.5.3.tgz#2be61ed7c349ad18dbcc947291bef9aa94ce26e1" + integrity sha512-c65YbQy65HyL2+KCZ0Xws54wzVlErVU2Ia1ewgkpEqwgtU/wb93pyCP2YOVI4K6jbIEXa6D1V0WsRM3XP9lmSQ== + +"@fontsource/libre-franklin@^4.1.0": + version "4.5.3" + resolved "https://registry.yarnpkg.com/@fontsource/libre-franklin/-/libre-franklin-4.5.3.tgz#89c519e60433cc63a473b31d5985ecba3ed89dac" + integrity sha512-dH3xW1RA1G8KPKAMv4FIQIzo04rT9uc+K3HzSNfxhrE0DpqIaABf7geUSV2GWtf5gjqdrjzrjLXjs0ehgkPrtQ== + +"@humanwhocodes/config-array@^0.9.2": + version "0.9.5" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" + integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" + integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + slash "^3.0.0" + +"@jest/core@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" + integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/reporters" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^27.5.1" + jest-config "^27.5.1" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-resolve-dependencies "^27.5.1" + jest-runner "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + jest-watcher "^27.5.1" + micromatch "^4.0.4" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" + integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== + dependencies: + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + +"@jest/fake-timers@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" + integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== + dependencies: + "@jest/types" "^27.5.1" + "@sinonjs/fake-timers" "^8.0.1" + "@types/node" "*" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-util "^27.5.1" + +"@jest/globals@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" + integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/types" "^27.5.1" + expect "^27.5.1" + +"@jest/reporters@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" + integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-haste-map "^27.5.1" + jest-resolve "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^8.1.0" + +"@jest/source-map@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" + integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.9" + source-map "^0.6.0" + +"@jest/test-result@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" + integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== + dependencies: + "@jest/console" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" + integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== + dependencies: + "@jest/test-result" "^27.5.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-runtime "^27.5.1" + +"@jest/transform@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" + integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^27.5.1" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-regex-util "^27.5.1" + jest-util "^27.5.1" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.0": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" + integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pmmmwh/react-refresh-webpack-plugin@^0.5.3": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.4.tgz#df0d0d855fc527db48aac93c218a0bf4ada41f99" + integrity sha512-zZbZeHQDnoTlt2AF+diQT0wsSXpvWiaIOZwBRdltNFhG1+I3ozyaw7U/nBiUwyJ0D+zwdXp0E3bWOl38Ag2BMw== + dependencies: + ansi-html-community "^0.0.8" + common-path-prefix "^3.0.0" + core-js-pure "^3.8.1" + error-stack-parser "^2.0.6" + find-up "^5.0.0" + html-entities "^2.1.0" + loader-utils "^2.0.0" + schema-utils "^3.0.0" + source-map "^0.7.3" + +"@react-spring/animated@~9.4.4": + version "9.4.4" + resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.4.4.tgz#15e21923e55c06ca2bcea432869b91b2f8b07519" + integrity sha512-e9xnuBaUTD+NolKikUmrGWjX8AVCPyj1GcEgjgq9E+0sXKv46UY7cm2EmB6mUDTxWIDVKebARY++xT4nGDraBQ== + dependencies: + "@react-spring/shared" "~9.4.4" + "@react-spring/types" "~9.4.4" + +"@react-spring/core@~9.4.4": + version "9.4.4" + resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.4.4.tgz#7730988cec7302ba6e0977cf4c08c30249d95622" + integrity sha512-llgb0ljFyjMB0JhWsaFHOi9XFT8n1jBMVs1IFY2ipIBerWIRWrgUmIpakLPHTa4c4jwqTaDSwX90s2a0iN7dxQ== + dependencies: + "@react-spring/animated" "~9.4.4" + "@react-spring/rafz" "~9.4.4" + "@react-spring/shared" "~9.4.4" + "@react-spring/types" "~9.4.4" + +"@react-spring/konva@~9.4.4": + version "9.4.4" + resolved "https://registry.yarnpkg.com/@react-spring/konva/-/konva-9.4.4.tgz#e65e3b85274e9af36c6037dbafa402d9f01db076" + integrity sha512-ZHwsf4l/W5YzK8TwlvGXL9SYiHxxC6iEOAKStRs8WV6VuBvTFgIoGK5RNOTbsRC2N/spNWnN6JViz1PNbgrB+A== + dependencies: + "@react-spring/animated" "~9.4.4" + "@react-spring/core" "~9.4.4" + "@react-spring/shared" "~9.4.4" + "@react-spring/types" "~9.4.4" + +"@react-spring/native@~9.4.4": + version "9.4.4" + resolved "https://registry.yarnpkg.com/@react-spring/native/-/native-9.4.4.tgz#8581e03d0c4a11f3e5a1fc8cf505c81f9d9a5fcf" + integrity sha512-p0/JI59JVkgVjnoLvu+cpEgEkE0B3RDvzT1oNaCAx0ePaGQo4ICAS8PyOgPvN5IHUOy59CBLSMNnFyHV+IgaGQ== + dependencies: + "@react-spring/animated" "~9.4.4" + "@react-spring/core" "~9.4.4" + "@react-spring/shared" "~9.4.4" + "@react-spring/types" "~9.4.4" + +"@react-spring/rafz@~9.4.4": + version "9.4.4" + resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.4.4.tgz#736c9ed1099baebeea20c357b9700b01b83ea9de" + integrity sha512-5ki/sQ06Mdf8AuFstSt5zbNNicRT4LZogiJttDAww1ozhuvemafNWEHxhzcULgCPCDu2s7HsroaISV7+GQWrhw== + +"@react-spring/shared@~9.4.4": + version "9.4.4" + resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.4.4.tgz#e1ae00a77d170d86d77d9a19dc7015bdddc2d26f" + integrity sha512-ySVgScDZlhm/+Iy2smY9i/DDrShArY0j6zjTS/Re1lasKnhq8qigoGiAxe8xMPJNlCaj3uczCqHy3TY9bKRtfQ== + dependencies: + "@react-spring/rafz" "~9.4.4" + "@react-spring/types" "~9.4.4" + +"@react-spring/three@~9.4.4": + version "9.4.4" + resolved "https://registry.yarnpkg.com/@react-spring/three/-/three-9.4.4.tgz#a26de44854e166184ddba57313bfc52efce67368" + integrity sha512-z77ohxg8zG0CcZJojzfoJTTrjSbIyefNz2RlId68/4IypnOs1p8kB2Q1p+wX4KyWORpLg8ivsPcjtwBjGwfDtg== + dependencies: + "@react-spring/animated" "~9.4.4" + "@react-spring/core" "~9.4.4" + "@react-spring/shared" "~9.4.4" + "@react-spring/types" "~9.4.4" + +"@react-spring/types@~9.4.4": + version "9.4.4" + resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.4.4.tgz#97c69881788e624d7cc68d4385fdaa9b5fd20642" + integrity sha512-KpxKt/D//q/t/6FBcde/RE36LKp8PpWu7kFEMLwpzMGl9RpcexunmYOQJWwmJWtkQjgE1YRr7DzBMryz6La1cQ== + +"@react-spring/web@~9.4.4": + version "9.4.4" + resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.4.4.tgz#0d13356e61c1d47e83a36112e19e5db691f3fbe5" + integrity sha512-iJmOLdhcuizriUlu/xqBc5y8KaFts+UI+iC+GxyTwBtzxA9czKiSAZW2ESuhG8stafa3jncwjfTQQp84KN36cw== + dependencies: + "@react-spring/animated" "~9.4.4" + "@react-spring/core" "~9.4.4" + "@react-spring/shared" "~9.4.4" + "@react-spring/types" "~9.4.4" + +"@react-spring/zdog@~9.4.4": + version "9.4.4" + resolved "https://registry.yarnpkg.com/@react-spring/zdog/-/zdog-9.4.4.tgz#e0e19175a9a22d74614dbf3c624513fd35e69f9d" + integrity sha512-qmD8zRcodbQKTAFVMdgW2pYIZP1KttDnz2S2JEc7kx8I8F5ljn9czgRl5c4w9HJ0dpO8VTfPq4sKa4tlUL23yg== + dependencies: + "@react-spring/animated" "~9.4.4" + "@react-spring/core" "~9.4.4" + "@react-spring/shared" "~9.4.4" + "@react-spring/types" "~9.4.4" + +"@recidiviz/design-system@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@recidiviz/design-system/-/design-system-2.7.0.tgz#25b67d141d88ab20548a08bf98284201b1ad1d34" + integrity sha512-Bnl/xGmY9k1QpHptKZa/QPIY99Cbmw1rxam70GRiZiKHY7ePdvHn8XucaFE1dJUyq/gDWC/KJSatJxZQpy1pgQ== + dependencies: + "@fontsource/libre-baskerville" "^4.1.0" + "@fontsource/libre-franklin" "^4.1.0" + "@types/react-tabs" "^2.3.2" + body-scroll-lock "^3.1.5" + polished "^4.1.0" + prop-types "^15.7.2" + react-is "^17.0.2" + react-modal "^3.12.1" + react-spring "^9.4.3" + react-tabs "^3.2.2" + react-toast-notifications "^2.4.3" + styled-components "^5.2.1" + +"@recidiviz/eslint-config@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@recidiviz/eslint-config/-/eslint-config-2.0.0.tgz#3b911264deb8ec2865dbaf8df4da8a8496c61583" + integrity sha512-r23ijnTDO43yODsEQYuuA25hNNnOJj/T6beMr7xsdOgw+Kd6iU+IKLr+lSpgIeBMUqK+YdyQRuXw4fdiQPWqlg== + dependencies: + eslint-config-airbnb "^18.2.0" + eslint-config-prettier "^8.1.0" + prettier "^2.2.1" + +"@recidiviz/tsconfig@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@recidiviz/tsconfig/-/tsconfig-1.0.0.tgz#317b6dc9a6c45016d10f4f4864b124d4ce5ba097" + integrity sha512-N6brrksfnQIYh7fqKhFhM+SQr817lXQdCgtHJyEvRL9rhtOR+1Y0b0nhVADZWEUSdRJdzjHoWWkOIJPoI9AluA== + dependencies: + "@tsconfig/node12" "^1.0.7" + +"@rollup/plugin-babel@^5.2.0": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" + integrity sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@rollup/pluginutils" "^3.1.0" + +"@rollup/plugin-node-resolve@^11.2.1": + version "11.2.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz#82aa59397a29cd4e13248b106e6a4a1880362a60" + integrity sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg== + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + builtin-modules "^3.1.0" + deepmerge "^4.2.2" + is-module "^1.0.0" + resolve "^1.19.0" + +"@rollup/plugin-replace@^2.4.1": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz#a2d539314fbc77c244858faa523012825068510a" + integrity sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg== + dependencies: + "@rollup/pluginutils" "^3.1.0" + magic-string "^0.25.7" + +"@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + +"@rushstack/eslint-patch@^1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.1.tgz#782fa5da44c4f38ae9fd38e9184b54e451936118" + integrity sha512-BUyKJGdDWqvWC5GEhyOiUrGNi9iJUr4CU0O2WxJL6QJhHeeA/NVBalH+FeK0r/x/W0rPymXt5s78TDS7d6lCwg== + +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@surma/rollup-plugin-off-main-thread@^2.2.3": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053" + integrity sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ== + dependencies: + ejs "^3.1.6" + json5 "^2.2.0" + magic-string "^0.25.0" + string.prototype.matchall "^4.0.6" + +"@svgr/babel-plugin-add-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" + integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== + +"@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz#6b2c770c95c874654fd5e1d5ef475b78a0a962ef" + integrity sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg== + +"@svgr/babel-plugin-remove-jsx-empty-expression@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz#25621a8915ed7ad70da6cea3d0a6dbc2ea933efd" + integrity sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz#0b221fc57f9fcd10e91fe219e2cd0dd03145a897" + integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== + +"@svgr/babel-plugin-svg-dynamic-title@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz#139b546dd0c3186b6e5db4fefc26cb0baea729d7" + integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== + +"@svgr/babel-plugin-svg-em-dimensions@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz#6543f69526632a133ce5cabab965deeaea2234a0" + integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== + +"@svgr/babel-plugin-transform-react-native-svg@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz#00bf9a7a73f1cad3948cdab1f8dfb774750f8c80" + integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== + +"@svgr/babel-plugin-transform-svg-component@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz#583a5e2a193e214da2f3afeb0b9e8d3250126b4a" + integrity sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ== + +"@svgr/babel-preset@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-5.5.0.tgz#8af54f3e0a8add7b1e2b0fcd5a882c55393df327" + integrity sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "^5.0.1" + "@svgr/babel-plugin-replace-jsx-attribute-value" "^5.0.1" + "@svgr/babel-plugin-svg-dynamic-title" "^5.4.0" + "@svgr/babel-plugin-svg-em-dimensions" "^5.4.0" + "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" + "@svgr/babel-plugin-transform-svg-component" "^5.5.0" + +"@svgr/core@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-5.5.0.tgz#82e826b8715d71083120fe8f2492ec7d7874a579" + integrity sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ== + dependencies: + "@svgr/plugin-jsx" "^5.5.0" + camelcase "^6.2.0" + cosmiconfig "^7.0.0" + +"@svgr/hast-util-to-babel-ast@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz#5ee52a9c2533f73e63f8f22b779f93cd432a5461" + integrity sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ== + dependencies: + "@babel/types" "^7.12.6" + +"@svgr/plugin-jsx@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz#1aa8cd798a1db7173ac043466d7b52236b369000" + integrity sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA== + dependencies: + "@babel/core" "^7.12.3" + "@svgr/babel-preset" "^5.5.0" + "@svgr/hast-util-to-babel-ast" "^5.5.0" + svg-parser "^2.0.2" + +"@svgr/plugin-svgo@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz#02da55d85320549324e201c7b2e53bf431fcc246" + integrity sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ== + dependencies: + cosmiconfig "^7.0.0" + deepmerge "^4.2.2" + svgo "^1.2.2" + +"@svgr/webpack@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-5.5.0.tgz#aae858ee579f5fa8ce6c3166ef56c6a1b381b640" + integrity sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g== + dependencies: + "@babel/core" "^7.12.3" + "@babel/plugin-transform-react-constant-elements" "^7.12.1" + "@babel/preset-env" "^7.12.1" + "@babel/preset-react" "^7.12.5" + "@svgr/core" "^5.5.0" + "@svgr/plugin-jsx" "^5.5.0" + "@svgr/plugin-svgo" "^5.5.0" + loader-utils "^2.0.0" + +"@testing-library/dom@^8.0.0", "@testing-library/dom@^8.11.3": + version "8.11.3" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.11.3.tgz#38fd63cbfe14557021e88982d931e33fb7c1a808" + integrity sha512-9LId28I+lx70wUiZjLvi1DB/WT2zGOxUh46glrSNMaWVx849kKAluezVzZrXJfTKKoQTmEOutLes/bHg4Bj3aA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^5.0.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.4.4" + pretty-format "^27.0.2" + +"@testing-library/jest-dom@^5.14.1": + version "5.16.2" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.2.tgz#f329b36b44aa6149cd6ced9adf567f8b6aa1c959" + integrity sha512-6ewxs1MXWwsBFZXIk4nKKskWANelkdUehchEOokHsN8X7c2eKXGw+77aRV63UU8f/DTSVUPLaGxdrj4lN7D/ug== + dependencies: + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^5.0.0" + chalk "^3.0.0" + css "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.5.6" + lodash "^4.17.15" + redent "^3.0.0" + +"@testing-library/react@^12.0.0": + version "12.1.4" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.4.tgz#09674b117e550af713db3f4ec4c0942aa8bbf2c0" + integrity sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^8.0.0" + "@types/react-dom" "*" + +"@testing-library/user-event@^13.2.1": + version "13.5.0" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" + integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg== + dependencies: + "@babel/runtime" "^7.12.5" + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@types/aria-query@^4.2.0": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" + integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== + +"@types/auth0@^2.34.13": + version "2.34.13" + resolved "https://registry.yarnpkg.com/@types/auth0/-/auth0-2.34.13.tgz#9bc2c114efff012a46fbcae94976958dd24c6c3e" + integrity sha512-eqTXGLGv4hLVMFaZOyZaXtjQqLnpgrcmyZXoRF99IQoOg1q/6KCy606+BRnqK6ywHGtI8fLk8Gjs2rvIFj0bWg== + +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": + version "7.1.18" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.18.tgz#1a29abcc411a9c05e2094c98f9a1b7da6cdf49f8" + integrity sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" + integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== + dependencies: + "@babel/types" "^7.3.0" + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.9": + version "3.5.10" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" + integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" + integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/eslint-scope@^3.7.3": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" + integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.4.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.1.tgz#c48251553e8759db9e656de3efc846954ac32304" + integrity sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/eslint@^7.28.2": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" + integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": + version "4.17.28" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" + integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/graceful-fs@^4.1.2": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== + +"@types/hoist-non-react-statics@*": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/html-minifier-terser@^6.0.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" + integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== + +"@types/http-proxy@^1.17.8": + version "1.17.8" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.8.tgz#968c66903e7e42b483608030ee85800f22d03f55" + integrity sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@*", "@types/jest@^27.0.1": + version "27.4.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.1.tgz#185cbe2926eaaf9662d340cc02e548ce9e11ab6d" + integrity sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw== + dependencies: + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" + +"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.10" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.10.tgz#9b05b7896166cd00e9cbd59864853abf65d9ac23" + integrity sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/lodash.debounce@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz#0285879defb7cdb156ae633cecd62d5680eded9f" + integrity sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*", "@types/lodash@^4.14.182": + version "4.14.182" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" + integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + +"@types/node@*": + version "17.0.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644" + integrity sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ== + +"@types/node@^16.7.13": + version "16.11.26" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.26.tgz#63d204d136c9916fb4dcd1b50f9740fe86884e47" + integrity sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/prettier@^2.1.5": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17" + integrity sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA== + +"@types/prop-types@*": + version "15.7.4" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" + integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== + +"@types/q@^1.5.1": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" + integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ== + +"@types/qs@*", "@types/qs@^6.9.7": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/reach__router@^1.3.10": + version "1.3.10" + resolved "https://registry.yarnpkg.com/@types/reach__router/-/reach__router-1.3.10.tgz#141d500213a452d9d9d71d5ad96c4104094f55a3" + integrity sha512-iHAFGaVOrWi00/q7oBybggGsz5TOmwOW4M1H9sT7i9lly4qFC8XOgsdf6jUsoaOz2sknFHALEtZqCoDbokdJ2Q== + dependencies: + "@types/react" "*" + +"@types/react-dom@*", "@types/react-dom@^17.0.9": + version "17.0.13" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.13.tgz#a3323b974ee4280070982b3112351bb1952a7809" + integrity sha512-wEP+B8hzvy6ORDv1QBhcQia4j6ea4SFIBttHYpXKPFZRviBvknq0FRh3VrIxeXUmsPkwuXVZrVGG7KUVONmXCQ== + dependencies: + "@types/react" "*" + +"@types/react-modal@^3.13.1": + version "3.13.1" + resolved "https://registry.yarnpkg.com/@types/react-modal/-/react-modal-3.13.1.tgz#5b9845c205fccc85d9a77966b6e16dc70a60825a" + integrity sha512-iY/gPvTDIy6Z+37l+ibmrY+GTV4KQTHcCyR5FIytm182RQS69G5ps4PH2FxtC7bAQ2QRHXMevsBgck7IQruHNg== + dependencies: + "@types/react" "*" + +"@types/react-router-dom@^5.3.3": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.18" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.18.tgz#c8851884b60bc23733500d86c1266e1cfbbd9ef3" + integrity sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + +"@types/react-tabs@^2.3.2": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@types/react-tabs/-/react-tabs-2.3.4.tgz#b63662ee52e59a86ec33b78b9721d8163a46394c" + integrity sha512-HQzhKW+RF/7h14APw/2cu4Nnt+GmsTvfBKbFdn/NbYpb8Q+iB65wIkPHz4VRKDxWtOpNFpOUtzt5r0LRmQMfOA== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^17.0.20": + version "17.0.40" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.40.tgz#dc010cee6254d5239a138083f3799a16638e6bad" + integrity sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/resolve@1.17.1": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" + integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== + dependencies: + "@types/node" "*" + +"@types/retry@^0.12.0": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" + integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +"@types/serve-index@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" + integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + dependencies: + "@types/express" "*" + +"@types/serve-static@*": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/sockjs@^0.3.33": + version "0.3.33" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" + integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== + dependencies: + "@types/node" "*" + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/styled-components@^5.1.24": + version "5.1.24" + resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.24.tgz#b52ae677f03ea8a6018aa34c6c96b7018b7a3571" + integrity sha512-mz0fzq2nez+Lq5IuYammYwWgyLUE6OMAJTQL9D8hFLP4Pkh7gVYJii/VQWxq8/TK34g/OrkehXaFNdcEKcItug== + dependencies: + "@types/hoist-non-react-statics" "*" + "@types/react" "*" + csstype "^3.0.2" + +"@types/testing-library__jest-dom@^5.9.1": + version "5.14.3" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz#ee6c7ffe9f8595882ee7bda8af33ae7b8789ef17" + integrity sha512-oKZe+Mf4ioWlMuzVBaXQ9WDnEm1+umLx0InILg+yvZVBBDmzV5KfZyLrCvadtWcx8+916jLmHafcmqqffl+iIw== + dependencies: + "@types/jest" "*" + +"@types/trusted-types@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" + integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== + +"@types/ws@^8.2.2": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.15.0", "@typescript-eslint/eslint-plugin@^5.5.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.15.0.tgz#c28ef7f2e688066db0b6a9d95fb74185c114fb9a" + integrity sha512-u6Db5JfF0Esn3tiAKELvoU5TpXVSkOpZ78cEGn/wXtT2RVqs2vkt4ge6N8cRCyw7YVKhmmLDbwI2pg92mlv7cA== + dependencies: + "@typescript-eslint/scope-manager" "5.15.0" + "@typescript-eslint/type-utils" "5.15.0" + "@typescript-eslint/utils" "5.15.0" + debug "^4.3.2" + functional-red-black-tree "^1.0.1" + ignore "^5.1.8" + regexpp "^3.2.0" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/experimental-utils@^5.0.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.15.0.tgz#407bbbdf1d11d24de81cfdf556b3a9f4252ba4ae" + integrity sha512-AJOOaBrVqKYWaYDBtgMi9XVDB3YHXlffto/3A4VQ39VVaNqosSOp/nW09G4N/ej8WlzHQB2jTnSfP5wWsXSQJA== + dependencies: + "@typescript-eslint/utils" "5.15.0" + +"@typescript-eslint/parser@^5.15.0", "@typescript-eslint/parser@^5.5.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.15.0.tgz#95f603f8fe6eca7952a99bfeef9b85992972e728" + integrity sha512-NGAYP/+RDM2sVfmKiKOCgJYPstAO40vPAgACoWPO/+yoYKSgAXIFaBKsV8P0Cc7fwKgvj27SjRNX4L7f4/jCKQ== + dependencies: + "@typescript-eslint/scope-manager" "5.15.0" + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/typescript-estree" "5.15.0" + debug "^4.3.2" + +"@typescript-eslint/scope-manager@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.15.0.tgz#d97afab5e0abf4018d1289bd711be21676cdd0ee" + integrity sha512-EFiZcSKrHh4kWk0pZaa+YNJosvKE50EnmN4IfgjkA3bTHElPtYcd2U37QQkNTqwMCS7LXeDeZzEqnsOH8chjSg== + dependencies: + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/visitor-keys" "5.15.0" + +"@typescript-eslint/type-utils@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.15.0.tgz#d2c02eb2bdf54d0a645ba3a173ceda78346cf248" + integrity sha512-KGeDoEQ7gHieLydujGEFLyLofipe9PIzfvA/41urz4hv+xVxPEbmMQonKSynZ0Ks2xDhJQ4VYjB3DnRiywvKDA== + dependencies: + "@typescript-eslint/utils" "5.15.0" + debug "^4.3.2" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.15.0.tgz#c7bdd103843b1abae97b5518219d3e2a0d79a501" + integrity sha512-yEiTN4MDy23vvsIksrShjNwQl2vl6kJeG9YkVJXjXZnkJElzVK8nfPsWKYxcsGWG8GhurYXP4/KGj3aZAxbeOA== + +"@typescript-eslint/typescript-estree@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.15.0.tgz#81513a742a9c657587ad1ddbca88e76c6efb0aac" + integrity sha512-Hb0e3dGc35b75xLzixM3cSbG1sSbrTBQDfIScqdyvrfJZVEi4XWAT+UL/HMxEdrJNB8Yk28SKxPLtAhfCbBInA== + dependencies: + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/visitor-keys" "5.15.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.15.0", "@typescript-eslint/utils@^5.13.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.15.0.tgz#468510a0974d3ced8342f37e6c662778c277f136" + integrity sha512-081rWu2IPKOgTOhHUk/QfxuFog8m4wxW43sXNOMSCdh578tGJ1PAaWPsj42LOa7pguh173tNlMigsbrHvh/mtA== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.15.0" + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/typescript-estree" "5.15.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.15.0.tgz#5669739fbf516df060f978be6a6dce75855a8027" + integrity sha512-+vX5FKtgvyHbmIJdxMJ2jKm9z2BIlXJiuewI8dsDYMp5LzPUcuTT78Ya5iwvQg3VqSVdmxyM8Anj1Jeq7733ZQ== + dependencies: + "@typescript-eslint/types" "5.15.0" + eslint-visitor-keys "^3.0.0" + +"@webassemblyjs/ast@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" + integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + +"@webassemblyjs/floating-point-hex-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" + integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== + +"@webassemblyjs/helper-api-error@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" + integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== + +"@webassemblyjs/helper-buffer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" + integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== + +"@webassemblyjs/helper-numbers@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" + integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" + integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== + +"@webassemblyjs/helper-wasm-section@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" + integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + +"@webassemblyjs/ieee754@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" + integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" + integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" + integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== + +"@webassemblyjs/wasm-edit@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" + integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-wasm-section" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-opt" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/wast-printer" "1.11.1" + +"@webassemblyjs/wasm-gen@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" + integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wasm-opt@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" + integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + +"@webassemblyjs/wasm-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" + integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wast-printer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" + integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +abab@^2.0.3, abab@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" + integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + +abortcontroller-polyfill@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" + integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-import-assertions@^1.7.6: + version "1.8.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" + integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== + +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^7.0.0, acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^7.0.0, acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + +address@^1.0.1, address@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" + integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== + +adjust-sourcemap-loader@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz#fc4a0fd080f7d10471f30a7320f25560ade28c99" + integrity sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A== + dependencies: + loader-utils "^2.0.0" + regex-parser "^2.2.11" + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.0, ajv@^8.6.0, ajv@^8.8.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.10.0.tgz#e573f719bd3af069017e3b66538ab968d040e54d" + integrity sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb" + integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== + dependencies: + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" + +aria-query@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" + integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +array-flatten@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +array-includes@^3.1.3, array-includes@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" + integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.flat@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" + integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + +array.prototype.flatmap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz#908dc82d8a406930fdf38598d51e7411d18d4446" + integrity sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.19.0" + +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +ast-types-flow@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= + +async@^2.6.2: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + +async@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" + integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autoprefixer@^10.0.2, autoprefixer@^10.4.2: + version "10.4.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.4.tgz#3e85a245b32da876a893d3ac2ea19f01e7ea5a1e" + integrity sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA== + dependencies: + browserslist "^4.20.2" + caniuse-lite "^1.0.30001317" + fraction.js "^4.2.0" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +axe-core@^4.3.5: + version "4.4.1" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413" + integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw== + +axobject-query@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" + integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== + +babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + +babel-jest@^27.4.2, babel-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" + integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== + dependencies: + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-loader@^8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.3.tgz#8986b40f1a64cacfcb4b8429320085ef68b1342d" + integrity sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw== + dependencies: + find-cache-dir "^3.3.1" + loader-utils "^1.4.0" + make-dir "^3.1.0" + schema-utils "^2.6.5" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-emotion@^10.0.27: + version "10.2.2" + resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz#a1fe3503cff80abfd0bdda14abd2e8e57a79d17d" + integrity sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@emotion/hash" "0.8.0" + "@emotion/memoize" "0.7.4" + "@emotion/serialize" "^0.11.16" + babel-plugin-macros "^2.0.0" + babel-plugin-syntax-jsx "^6.18.0" + convert-source-map "^1.5.0" + escape-string-regexp "^1.0.5" + find-root "^1.1.0" + source-map "^0.5.7" + +babel-plugin-import@^1.13.3: + version "1.13.3" + resolved "https://registry.yarnpkg.com/babel-plugin-import/-/babel-plugin-import-1.13.3.tgz#9dbbba7d1ac72bd412917a830d445e00941d26d7" + integrity sha512-1qCWdljJOrDRH/ybaCZuDgySii4yYrtQ8OJQwrcDqdt0y67N30ng3X3nABg6j7gR7qUJgcMa9OMhc4AGViDwWw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/runtime" "^7.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" + integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-macros@^2.0.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" + integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== + dependencies: + "@babel/runtime" "^7.7.2" + cosmiconfig "^6.0.0" + resolve "^1.12.0" + +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + +babel-plugin-named-asset-import@^0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz#6b7fa43c59229685368683c28bc9734f24524cc2" + integrity sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q== + +babel-plugin-polyfill-corejs2@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5" + integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w== + dependencies: + "@babel/compat-data" "^7.13.11" + "@babel/helper-define-polyfill-provider" "^0.3.1" + semver "^6.1.1" + +babel-plugin-polyfill-corejs3@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72" + integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.1" + core-js-compat "^3.21.0" + +babel-plugin-polyfill-regenerator@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" + integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.1" + +"babel-plugin-styled-components@>= 1.12.0": + version "2.0.6" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.6.tgz#6f76c7f7224b7af7edc24a4910351948c691fc90" + integrity sha512-Sk+7o/oa2HfHv3Eh8sxoz75/fFvEdHsXV4grdeHufX0nauCmymlnN0rGhIvfpMQSJMvGutJ85gvCGea4iqmDpg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.0" + "@babel/helper-module-imports" "^7.16.0" + babel-plugin-syntax-jsx "^6.18.0" + lodash "^4.17.11" + picomatch "^2.3.0" + +babel-plugin-syntax-jsx@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= + +babel-plugin-transform-react-remove-prop-types@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a" + integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA== + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" + integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== + dependencies: + babel-plugin-jest-hoist "^27.5.1" + babel-preset-current-node-syntax "^1.0.0" + +babel-preset-react-app@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz#ed6005a20a24f2c88521809fa9aea99903751584" + integrity sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg== + dependencies: + "@babel/core" "^7.16.0" + "@babel/plugin-proposal-class-properties" "^7.16.0" + "@babel/plugin-proposal-decorators" "^7.16.4" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.0" + "@babel/plugin-proposal-numeric-separator" "^7.16.0" + "@babel/plugin-proposal-optional-chaining" "^7.16.0" + "@babel/plugin-proposal-private-methods" "^7.16.0" + "@babel/plugin-transform-flow-strip-types" "^7.16.0" + "@babel/plugin-transform-react-display-name" "^7.16.0" + "@babel/plugin-transform-runtime" "^7.16.4" + "@babel/preset-env" "^7.16.4" + "@babel/preset-react" "^7.16.0" + "@babel/preset-typescript" "^7.16.0" + "@babel/runtime" "^7.16.3" + babel-plugin-macros "^3.1.0" + babel-plugin-transform-react-remove-prop-types "^0.4.24" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= + +bfj@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2" + integrity sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw== + dependencies: + bluebird "^3.5.5" + check-types "^11.1.1" + hoopy "^0.1.4" + tryer "^1.0.1" + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +body-parser@1.19.2: + version "1.19.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" + integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.8.1" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.9.7" + raw-body "2.4.3" + type-is "~1.6.18" + +body-scroll-lock@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/body-scroll-lock/-/body-scroll-lock-3.1.5.tgz#c1392d9217ed2c3e237fee1e910f6cdd80b7aaec" + integrity sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg== + +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.1, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browser-tabs-lock@^1.2.15: + version "1.2.15" + resolved "https://registry.yarnpkg.com/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz#d5012e652e2a0cb4eba471b0a2300c2fa5d92788" + integrity sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA== + dependencies: + lodash ">=4.17.21" + +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.18.1, browserslist@^4.19.1, browserslist@^4.19.3, browserslist@^4.20.2: + version "4.20.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88" + integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== + dependencies: + caniuse-lite "^1.0.30001317" + electron-to-chromium "^1.4.84" + escalade "^3.1.1" + node-releases "^2.0.2" + picocolors "^1.0.0" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== + +builtin-modules@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" + integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0, camelcase@^6.2.1: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +camelize@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" + integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001317: + version "1.0.30001317" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz#0548fb28fd5bc259a70b8c1ffdbe598037666a1b" + integrity sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ== + +case-sensitive-paths-webpack-plugin@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" + integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== + +chalk@^2.0.0, chalk@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +char-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e" + integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw== + +charcodes@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/charcodes/-/charcodes-0.2.0.tgz#5208d327e6cc05f99eb80ffc814707572d1f14e4" + integrity sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ== + +check-types@^11.1.1: + version "11.1.2" + resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f" + integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ== + +chokidar@^3.4.2, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +ci-info@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + +classnames@^2.2.5: + version "2.3.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== + +clean-css@^5.2.2: + version "5.2.4" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.4.tgz#982b058f8581adb2ae062520808fb2429bd487a4" + integrity sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg== + dependencies: + source-map "~0.6.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clsx@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" + integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + q "^1.1.2" + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@^1.1.4, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colord@^2.9.1: + version "2.9.2" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1" + integrity sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ== + +colorette@^2.0.10: + version "2.0.16" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" + integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +common-path-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" + integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== + +common-tags@^1.8.0: + version "1.8.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +confusing-browser-globals@^1.0.10, confusing-browser-globals@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" + integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== + +connect-history-api-fallback@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +core-js-compat@^3.20.2, core-js-compat@^3.21.0: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.21.1.tgz#cac369f67c8d134ff8f9bd1623e3bc2c42068c82" + integrity sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g== + dependencies: + browserslist "^4.19.1" + semver "7.0.0" + +core-js-pure@^3.20.2, core-js-pure@^3.8.1: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51" + integrity sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ== + +core-js@^3.19.2, core-js@^3.20.3: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94" + integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + +cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cross-fetch@^3.0.4: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +css-blank-pseudo@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561" + integrity sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ== + dependencies: + postcss-selector-parser "^6.0.9" + +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU= + +css-declaration-sorter@^6.0.3: + version "6.1.4" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.4.tgz#b9bfb4ed9a41f8dcca9bf7184d849ea94a8294b4" + integrity sha512-lpfkqS0fctcmZotJGhnxkIyJWvBXgpyi2wsFd4J8VB7wzyrT6Ch/3Q+FMNJpjK4gu1+GN5khOnpU2ZVKrLbhCw== + dependencies: + timsort "^0.3.0" + +css-has-pseudo@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz#57f6be91ca242d5c9020ee3e51bbb5b89fc7af73" + integrity sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw== + dependencies: + postcss-selector-parser "^6.0.9" + +css-loader@^6.5.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" + integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.7" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.3.5" + +css-minimizer-webpack-plugin@^3.2.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz#ab78f781ced9181992fe7b6e4f3422e76429878f" + integrity sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q== + dependencies: + cssnano "^5.0.6" + jest-worker "^27.0.2" + postcss "^8.3.5" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + +css-prefers-color-scheme@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz#ca8a22e5992c10a5b9d315155e7caee625903349" + integrity sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA== + +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + +css-select@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + +css-select@^4.1.3: + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" + integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== + dependencies: + boolbase "^1.0.0" + css-what "^5.1.0" + domhandler "^4.3.0" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-to-react-native@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" + integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-unit-converter@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" + integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== + +css-what@^3.2.1: + version "3.4.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" + integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== + +css-what@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== + +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= + +css@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" + integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== + dependencies: + inherits "^2.0.4" + source-map "^0.6.1" + source-map-resolve "^0.6.0" + +cssdb@^6.4.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-6.5.0.tgz#61264b71f29c834f09b59cb3e5b43c8226590122" + integrity sha512-Rh7AAopF2ckPXe/VBcoUS9JrCZNSyc60+KpgE6X25vpVxA32TmiqvExjkfhwP4wGSb6Xe8Z/JIyGqwgx/zZYFA== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^*: + version "5.2.4" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.4.tgz#eced79bbc1ab7270337c4038a21891daac2329bc" + integrity sha512-w1Gg8xsebln6/axZ6qDFQHuglrGfbIHOIx0g4y9+etRlRab8CGpSpe6UMsrgJe4zhCaJ0LwLmc+PhdLRTwnhIA== + dependencies: + css-declaration-sorter "^6.0.3" + cssnano-utils "^*" + postcss-calc "^8.2.3" + postcss-colormin "^*" + postcss-convert-values "^*" + postcss-discard-comments "^*" + postcss-discard-duplicates "^*" + postcss-discard-empty "^*" + postcss-discard-overridden "^*" + postcss-merge-longhand "^*" + postcss-merge-rules "^*" + postcss-minify-font-values "^*" + postcss-minify-gradients "^*" + postcss-minify-params "^*" + postcss-minify-selectors "^*" + postcss-normalize-charset "^*" + postcss-normalize-display-values "^*" + postcss-normalize-positions "^*" + postcss-normalize-repeat-style "^*" + postcss-normalize-string "^*" + postcss-normalize-timing-functions "^*" + postcss-normalize-unicode "^*" + postcss-normalize-url "^*" + postcss-normalize-whitespace "^*" + postcss-ordered-values "^*" + postcss-reduce-initial "^*" + postcss-reduce-transforms "^*" + postcss-svgo "^*" + postcss-unique-selectors "^*" + +cssnano-utils@^*, cssnano-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" + integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== + +cssnano@^5.0.6: + version "5.1.4" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.4.tgz#c648192e8e2f1aacb7d839e6aa3706b50cc7f8e4" + integrity sha512-hbfhVZreEPyzl+NbvRsjNo54JOX80b+j6nqG2biLVLaZHJEiqGyMh4xDGHtwhUKd5p59mj2GlDqlUBwJUuIu5A== + dependencies: + cssnano-preset-default "^*" + lilconfig "^2.0.3" + yaml "^1.10.2" + +csso@^4.0.2, csso@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +csstype@^2.5.7: + version "2.6.20" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda" + integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA== + +csstype@^3.0.2: + version "3.0.11" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" + integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== + +"d3-array@2 - 3", "d3-array@2.10.0 - 3": + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.0.tgz#15bf96cd9b7333e02eb8de8053d78962eafcff14" + integrity sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g== + dependencies: + internmap "1 - 2" + +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +"d3-format@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +"d3-path@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e" + integrity sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w== + +d3-scale@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +d3-shape@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.1.0.tgz#c8a495652d83ea6f524e482fca57aa3f8bc32556" + integrity sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ== + dependencies: + d3-path "1 - 3" + +"d3-time-format@2 - 4": + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3": + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975" + integrity sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ== + dependencies: + d3-array "2 - 3" + +damerau-levenshtein@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== + +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + +debug@2.6.9, debug@^2.6.0, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + +debug@^3.1.1, debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +decimal.js-light@^2.4.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + +decimal.js@^10.2.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + +deep-equal@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + +deep-is@^0.1.3, deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +del@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952" + integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ== + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +detect-port-alt@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" + integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== + dependencies: + address "^1.0.1" + debug "^2.6.0" + +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= + +dns-packet@^1.3.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f" + integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= + dependencies: + buffer-indexof "^1.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: + version "0.5.13" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.13.tgz#102ee5f25eacce09bdf1cfa5a298f86da473be4b" + integrity sha512-R305kwb5CcMDIpSHUnLyIAp7SrSPBx6F0VfQFB3M75xVMHhXJJIdePYgbPPh1o57vCHNu5QztokWUPsLjWzFqw== + +dom-converter@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + +dom-helpers@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" + integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== + dependencies: + "@babel/runtime" "^7.1.2" + +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +domelementtype@1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + +domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" + integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== + dependencies: + domelementtype "^2.2.0" + +domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^2.5.2, domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +ejs@^3.1.6: + version "3.1.7" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.7.tgz#c544d9c7f715783dd92f0bddcf73a59e6962d006" + integrity sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.4.84: + version "1.4.85" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.85.tgz#a3666ba42147026b9f34d4d8d4caf0740e80f751" + integrity sha512-K9AsQ41WS2bjZUFpRWfvaS4RjEcRCamEkBJN1Z1TQILBfP1H8QnJ9ti0wiLiMv0sRjX3EHKzgs9jDnmGFx2jXg== + +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +enhanced-resolve@^5.9.2: + version "5.9.2" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9" + integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +error-stack-parser@^2.0.6: + version "2.0.7" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.7.tgz#b0c6e2ce27d0495cf78ad98715e0cad1219abb57" + integrity sha512-chLOW0ZGRf4s8raLrDxa5sdkvPec5YdvwbFnqJme4rk0rFajP8mPtrDL1+I+CwrQDCjswDA5sREX7jYQDQs9vA== + dependencies: + stackframe "^1.1.1" + +es-abstract@^1.17.2, es-abstract@^1.19.0, es-abstract@^1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" + integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.1" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.1" + is-string "^1.0.7" + is-weakref "^1.0.1" + object-inspect "^1.11.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-cookie@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/es-cookie/-/es-cookie-1.3.2.tgz#80e831597f72a25721701bdcb21d990319acd831" + integrity sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q== + +es-module-lexer@^0.9.0: + version "0.9.3" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" + integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-config-airbnb-base@^14.2.1: + version "14.2.1" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e" + integrity sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA== + dependencies: + confusing-browser-globals "^1.0.10" + object.assign "^4.1.2" + object.entries "^1.1.2" + +eslint-config-airbnb-base@^15.0.0: + version "15.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" + integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== + dependencies: + confusing-browser-globals "^1.0.10" + object.assign "^4.1.2" + object.entries "^1.1.5" + semver "^6.3.0" + +eslint-config-airbnb@^18.2.0: + version "18.2.1" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-18.2.1.tgz#b7fe2b42f9f8173e825b73c8014b592e449c98d9" + integrity sha512-glZNDEZ36VdlZWoxn/bUR1r/sdFKPd1mHPbqUtkctgNG4yT2DLLtJ3D+yCV+jzZCc2V1nBVkmdknOJBZ5Hc0fg== + dependencies: + eslint-config-airbnb-base "^14.2.1" + object.assign "^4.1.2" + object.entries "^1.1.2" + +eslint-config-airbnb@^19.0.4: + version "19.0.4" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz#84d4c3490ad70a0ffa571138ebcdea6ab085fdc3" + integrity sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew== + dependencies: + eslint-config-airbnb-base "^15.0.0" + object.assign "^4.1.2" + object.entries "^1.1.5" + +eslint-config-prettier@^8.1.0, eslint-config-prettier@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" + integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + +eslint-config-react-app@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-7.0.0.tgz#0fa96d5ec1dfb99c029b1554362ab3fa1c3757df" + integrity sha512-xyymoxtIt1EOsSaGag+/jmcywRuieQoA2JbPCjnw9HukFj9/97aGPoZVFioaotzk1K5Qt9sHO5EutZbkrAXS0g== + dependencies: + "@babel/core" "^7.16.0" + "@babel/eslint-parser" "^7.16.3" + "@rushstack/eslint-patch" "^1.1.0" + "@typescript-eslint/eslint-plugin" "^5.5.0" + "@typescript-eslint/parser" "^5.5.0" + babel-preset-react-app "^10.0.1" + confusing-browser-globals "^1.0.11" + eslint-plugin-flowtype "^8.0.3" + eslint-plugin-import "^2.25.3" + eslint-plugin-jest "^25.3.0" + eslint-plugin-jsx-a11y "^6.5.1" + eslint-plugin-react "^7.27.1" + eslint-plugin-react-hooks "^4.3.0" + eslint-plugin-testing-library "^5.0.1" + +eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + +eslint-import-resolver-typescript@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz#07661966b272d14ba97f597b51e1a588f9722f0a" + integrity sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ== + dependencies: + debug "^4.3.1" + glob "^7.1.7" + is-glob "^4.0.1" + resolve "^1.20.0" + tsconfig-paths "^3.9.0" + +eslint-module-utils@^2.7.2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" + integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== + dependencies: + debug "^3.2.7" + find-up "^2.1.0" + +eslint-plugin-flowtype@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz#e1557e37118f24734aa3122e7536a038d34a4912" + integrity sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ== + dependencies: + lodash "^4.17.21" + string-natural-compare "^3.0.1" + +eslint-plugin-import@^2.25.3, eslint-plugin-import@^2.25.4: + version "2.25.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== + dependencies: + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.7.2" + has "^1.0.3" + is-core-module "^2.8.0" + is-glob "^4.0.3" + minimatch "^3.0.4" + object.values "^1.1.5" + resolve "^1.20.0" + tsconfig-paths "^3.12.0" + +eslint-plugin-jest@^25.3.0: + version "25.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz#ff4ac97520b53a96187bad9c9814e7d00de09a6a" + integrity sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ== + dependencies: + "@typescript-eslint/experimental-utils" "^5.0.0" + +eslint-plugin-jsx-a11y@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz#cdbf2df901040ca140b6ec14715c988889c2a6d8" + integrity sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g== + dependencies: + "@babel/runtime" "^7.16.3" + aria-query "^4.2.2" + array-includes "^3.1.4" + ast-types-flow "^0.0.7" + axe-core "^4.3.5" + axobject-query "^2.2.0" + damerau-levenshtein "^1.0.7" + emoji-regex "^9.2.2" + has "^1.0.3" + jsx-ast-utils "^3.2.1" + language-tags "^1.0.5" + minimatch "^3.0.4" + +eslint-plugin-prettier@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz#8b99d1e4b8b24a762472b4567992023619cb98e0" + integrity sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-react-hooks@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172" + integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA== + +eslint-plugin-react@^7.27.1, eslint-plugin-react@^7.29.4: + version "7.29.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz#4717de5227f55f3801a5fd51a16a4fa22b5914d2" + integrity sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ== + dependencies: + array-includes "^3.1.4" + array.prototype.flatmap "^1.2.5" + doctrine "^2.1.0" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.5" + object.fromentries "^2.0.5" + object.hasown "^1.1.0" + object.values "^1.1.5" + prop-types "^15.8.1" + resolve "^2.0.0-next.3" + semver "^6.3.0" + string.prototype.matchall "^4.0.6" + +eslint-plugin-simple-import-sort@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-7.0.0.tgz#a1dad262f46d2184a90095a60c66fef74727f0f8" + integrity sha512-U3vEDB5zhYPNfxT5TYR7u01dboFZp+HNpnGhkDB2g/2E4wZ/g1Q9Ton8UwCLfRV9yAKyYqDh62oHOamvkFxsvw== + +eslint-plugin-testing-library@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.1.0.tgz#6ad539a53d4e897d3045902f8e534e07cebd4e8b" + integrity sha512-YSNzasJUbyhOTe14ZPygeOBvcPvcaNkwHwrj4vdf+uirr2D32JTDaKi6CP5Os2aWtOcvt4uBSPXp9h5xGoqvWQ== + dependencies: + "@typescript-eslint/utils" "^5.13.0" + +eslint-scope@5.1.1, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint-webpack-plugin@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-3.1.1.tgz#83dad2395e5f572d6f4d919eedaa9cf902890fcb" + integrity sha512-xSucskTN9tOkfW7so4EaiFIkulWLXwCB/15H917lR6pTv0Zot6/fetFucmENRb7J5whVSFKIvwnrnsa78SG2yg== + dependencies: + "@types/eslint" "^7.28.2" + jest-worker "^27.3.1" + micromatch "^4.0.4" + normalize-path "^3.0.0" + schema-utils "^3.1.1" + +eslint@^8.11.0, eslint@^8.3.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.11.0.tgz#88b91cfba1356fc10bb9eb592958457dfe09fb37" + integrity sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA== + dependencies: + "@eslint/eslintrc" "^1.2.1" + "@humanwhocodes/config-array" "^0.9.2" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.1" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^6.0.1" + globals "^13.6.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" + integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== + dependencies: + acorn "^8.7.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^3.3.0" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +eventemitter3@^4.0.0, eventemitter3@^4.0.1: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exenv@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expect@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== + dependencies: + "@jest/types" "^27.5.1" + jest-get-type "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + +express@^4.17.1: + version "4.17.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" + integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.19.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.4.2" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.9.7" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.17.2" + serve-static "1.14.2" + setprototypeof "1.2.0" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-equals@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-2.0.4.tgz#3add9410585e2d7364c2deeb6a707beadb24b927" + integrity sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w== + +fast-glob@^3.2.11, fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fast-text-encoding@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" + integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +filelist@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.3.tgz#448607750376484932f67ef1b9ff07386b036c83" + integrity sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q== + dependencies: + minimatch "^5.0.1" + +filesize@^8.0.6: + version "8.0.7" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-cache-dir@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.5" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" + integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== + +follow-redirects@^1.0.0: + version "1.14.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" + integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== + +fork-ts-checker-webpack-plugin@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz#0282b335fa495a97e167f69018f566ea7d2a2b5e" + integrity sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw== + dependencies: + "@babel/code-frame" "^7.8.3" + "@types/json-schema" "^7.0.5" + chalk "^4.1.0" + chokidar "^3.4.2" + cosmiconfig "^6.0.0" + deepmerge "^4.2.2" + fs-extra "^9.0.0" + glob "^7.1.6" + memfs "^3.1.2" + minimatch "^3.0.4" + schema-utils "2.7.0" + semver "^7.3.2" + tapable "^1.0.0" + +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fraction.js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" + integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-extra@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.1.tgz#27de43b4320e833f6867cc044bfce29fdf0ef3b8" + integrity sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.0.0, fs-extra@^9.0.1: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-monkey@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" + integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1, glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.6.0, globals@^13.9.0: + version "13.13.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.13.0.tgz#ac32261060d8070e2719dd6998406e27d2b5727b" + integrity sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A== + dependencies: + type-fest "^0.20.2" + +globby@^11.0.1, globby@^11.0.4: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +harmony-reflect@^1.4.6: + version "1.6.2" + resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" + integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g== + +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +history@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" + integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== + dependencies: + "@babel/runtime" "^7.7.6" + +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hoopy@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" + integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + +html-entities@^2.1.0, html-entities@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488" + integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-minifier-terser@^6.0.2: + version "6.1.0" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" + integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== + dependencies: + camel-case "^4.1.2" + clean-css "^5.2.2" + commander "^8.3.0" + he "^1.2.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.10.0" + +html-webpack-plugin@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" + integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== + dependencies: + "@types/html-minifier-terser" "^6.0.0" + html-minifier-terser "^6.0.2" + lodash "^4.17.21" + pretty-error "^4.0.0" + tapable "^2.0.0" + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + +http-errors@1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.1" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.6.tgz#2e02406ab2df8af8a7abfba62e0da01c62b95afd" + integrity sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA== + +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +http-proxy-middleware@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz#03af0f4676d172ae775cb5c33f592f40e1a4e07a" + integrity sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + +idb@^6.1.4: + version "6.1.5" + resolved "https://registry.yarnpkg.com/idb/-/idb-6.1.5.tgz#dbc53e7adf1ac7c59f9b2bf56e00b4ea4fce8c7b" + integrity sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw== + +identity-obj-proxy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" + integrity sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ= + dependencies: + harmony-reflect "^1.4.6" + +ignore@^5.1.8, ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +immer@^9.0.7: + version "9.0.12" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" + integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== + +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@^1.3.5: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + +ip@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +ipaddr.js@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" + integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== + +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-core-module@^2.2.0, is-core-module@^2.8.0, is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + +is-negative-zero@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + +is-path-cwd@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + +is-regex@^1.0.4, is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + +is-root@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" + integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== + +is-shared-array-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" + integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-weakref@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" + integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.4" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" + integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.1" + minimatch "^3.0.4" + +jest-changed-files@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" + integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== + dependencies: + "@jest/types" "^27.5.1" + execa "^5.0.0" + throat "^6.0.1" + +jest-circus@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" + integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" + integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== + dependencies: + "@jest/core" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + prompts "^2.0.1" + yargs "^16.2.0" + +jest-config@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" + integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== + dependencies: + "@babel/core" "^7.8.0" + "@jest/test-sequencer" "^27.5.1" + "@jest/types" "^27.5.1" + babel-jest "^27.5.1" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.9" + jest-circus "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-get-type "^27.5.1" + jest-jasmine2 "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runner "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^27.5.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-docblock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" + integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== + dependencies: + detect-newline "^3.0.0" + +jest-each@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" + integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== + dependencies: + "@jest/types" "^27.5.1" + chalk "^4.0.0" + jest-get-type "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + +jest-environment-jsdom@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" + integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + jest-util "^27.5.1" + jsdom "^16.6.0" + +jest-environment-node@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" + integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + jest-util "^27.5.1" + +jest-fetch-mock@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" + integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== + dependencies: + cross-fetch "^3.0.4" + promise-polyfill "^8.1.3" + +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + +jest-haste-map@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" + integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== + dependencies: + "@jest/types" "^27.5.1" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^27.5.1" + jest-serializer "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + micromatch "^4.0.4" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + +jest-jasmine2@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" + integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + throat "^6.0.1" + +jest-leak-detector@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" + integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== + dependencies: + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-message-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.5.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^27.0.0, jest-regex-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" + integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== + +jest-resolve-dependencies@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" + integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== + dependencies: + "@jest/types" "^27.5.1" + jest-regex-util "^27.5.1" + jest-snapshot "^27.5.1" + +jest-resolve@^27.4.2, jest-resolve@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" + integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== + dependencies: + "@jest/types" "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-pnp-resolver "^1.2.2" + jest-util "^27.5.1" + jest-validate "^27.5.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" + integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.8.1" + graceful-fs "^4.2.9" + jest-docblock "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-haste-map "^27.5.1" + jest-leak-detector "^27.5.1" + jest-message-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runtime "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + source-map-support "^0.5.6" + throat "^6.0.1" + +jest-runtime@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" + integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/globals" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-serializer@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" + integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.9" + +jest-snapshot@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" + integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.0.0" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^27.5.1" + graceful-fs "^4.2.9" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + jest-haste-map "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + natural-compare "^1.4.0" + pretty-format "^27.5.1" + semver "^7.3.2" + +jest-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" + integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== + dependencies: + "@jest/types" "^27.5.1" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^27.5.1" + leven "^3.1.0" + pretty-format "^27.5.1" + +jest-watch-typeahead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-1.0.0.tgz#4de2ca1eb596acb1889752afbab84b74fcd99173" + integrity sha512-jxoszalAb394WElmiJTFBMzie/RDCF+W7Q29n5LzOPtcoQoHWfdUtHFkbhgf5NwWe8uMOxvKb/g7ea7CshfkTw== + dependencies: + ansi-escapes "^4.3.1" + chalk "^4.0.0" + jest-regex-util "^27.0.0" + jest-watcher "^27.0.0" + slash "^4.0.0" + string-length "^5.0.1" + strip-ansi "^7.0.1" + +jest-watcher@^27.0.0, jest-watcher@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" + integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== + dependencies: + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^27.5.1" + string-length "^4.0.1" + +jest-worker@^26.2.1: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest-worker@^27.0.2, jest-worker@^27.3.1, jest-worker@^27.4.5, jest-worker@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^27.4.3: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" + integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== + dependencies: + "@jest/core" "^27.5.1" + import-local "^3.0.2" + jest-cli "^27.5.1" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsdom@^16.6.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== + dependencies: + abab "^2.0.5" + acorn "^8.2.4" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.3.0" + data-urls "^2.0.0" + decimal.js "^10.2.1" + domexception "^2.0.1" + escodegen "^2.0.0" + form-data "^3.0.0" + html-encoding-sniffer "^2.0.1" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.5.0" + ws "^7.4.6" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-schema@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2, json5@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonpointer@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.0.tgz#f802669a524ec4805fa7389eadbc9921d5dc8072" + integrity sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg== + +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b" + integrity sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA== + dependencies: + array-includes "^3.1.3" + object.assign "^4.1.2" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +klona@^2.0.4, klona@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" + integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== + +language-subtag-registry@~0.3.2: + version "0.3.21" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" + integrity sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg== + +language-tags@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= + dependencies: + language-subtag-registry "~0.3.2" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lilconfig@^2.0.3, lilconfig@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" + integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +loader-runner@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" + integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== + +loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +loader-utils@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" + integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +loader-utils@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.0.tgz#bcecc51a7898bee7473d4bc6b845b23af8304d4f" + integrity sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ== + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash.mapvalues@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c" + integrity sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ== + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.pickby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" + integrity sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q== + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@>=4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lz-string@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" + integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= + +magic-string@^0.25.0, magic-string@^0.25.7: + version "0.25.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +memfs@^3.1.2, memfs@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305" + integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw== + dependencies: + fs-monkey "1.0.3" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +mini-css-extract-plugin@^2.4.5: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz#578aebc7fc14d32c0ad304c2c34f08af44673f5e" + integrity sha512-ndG8nxCEnAemsg4FSgS+yNyHKgkTB4nPKqCOgh65j3/30qqC5RaSQQXMm++Y6sb6E1zRSxPkztj9fqxhS1Eo6w== + dependencies: + schema-utils "^4.0.0" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.4, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +mkdirp@^0.5.5, mkdirp@~0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mobx-persist-store@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/mobx-persist-store/-/mobx-persist-store-1.0.6.tgz#23e45332f638945d147e7158d0761843616a75cb" + integrity sha512-GOV1/0H2bMQhsJKpwRrR74c+B7wl9sOSwMeq7ed1PPsIanW/aYVj0RaQDqVOTYKJfeLV9wNGnWk0EATWprNluw== + +mobx-react-lite@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.3.0.tgz#7174e807201943beff6f9d3701492314c9fc0db3" + integrity sha512-U/kMSFtV/bNVgY01FuiGWpRkaQVHozBq5CEBZltFvPt4FcV111hEWkgwqVg9GPPZSEuEdV438PEz8mk8mKpYlA== + +mobx@^6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.4.2.tgz#d25cd358a46b7a8fe2d8299259bc71008a2aa5b3" + integrity sha512-b4xQJYiH8sb0sEbfq/Ws3N77DEJtSihUFD1moeiz2jNoJ5B+mqJutt54ouO9iEfkp7Wk4jQDsVUOh7DPEW3wEw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= + +multicast-dns@^6.0.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" + integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== + dependencies: + dns-packet "^1.3.1" + thunky "^1.0.2" + +nanoid@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" + integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +node-forge@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-releases@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" + integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nth-check@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +nth-check@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + dependencies: + boolbase "^1.0.0" + +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + +object-is@^1.0.1: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries@^1.1.2, object.entries@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861" + integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.fromentries@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.5.tgz#7b37b205109c21e741e605727fe8b0ad5fa08251" + integrity sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.getownpropertydescriptors@^2.1.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" + integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.hasown@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.0.tgz#7232ed266f34d197d15cac5880232f7a4790afe5" + integrity sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.values@^1.1.0, object.values@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^8.0.9, open@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-retry@^4.5.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" + integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== + dependencies: + "@types/retry" "^0.12.0" + retry "^0.13.1" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +param-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0, parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6, path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +picocolors@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" + integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +polished@^4.1.0: + version "4.1.4" + resolved "https://registry.yarnpkg.com/polished/-/polished-4.1.4.tgz#640293ba834109614961a700fdacbb6599fb12d0" + integrity sha512-Nq5Mbza+Auo7N3sQb1QMFaQiDO+4UexWuSGR7Cjb4Sw11SZIJcrrFtiZ+L0jT9MBsUsxDboHVASbCLbE1rnECg== + dependencies: + "@babel/runtime" "^7.16.7" + +portfinder@^1.0.28: + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== + dependencies: + async "^2.6.2" + debug "^3.1.1" + mkdirp "^0.5.5" + +postcss-attribute-case-insensitive@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.0.tgz#39cbf6babf3ded1e4abf37d09d6eda21c644105c" + integrity sha512-b4g9eagFGq9T5SWX4+USfVyjIb3liPnjhHHRMP7FMB2kFVpYyfEscV0wP3eaXhKlcHKUut8lt5BGoeylWA/dBQ== + dependencies: + postcss-selector-parser "^6.0.2" + +postcss-browser-comments@^4: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz#bcfc86134df5807f5d3c0eefa191d42136b5e72a" + integrity sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg== + +postcss-calc@^8.2.3: + version "8.2.4" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" + integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== + dependencies: + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" + +postcss-color-functional-notation@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.2.tgz#f59ccaeb4ee78f1b32987d43df146109cc743073" + integrity sha512-DXVtwUhIk4f49KK5EGuEdgx4Gnyj6+t2jBSEmxvpIK9QI40tWrpS2Pua8Q7iIZWBrki2QOaeUdEaLPPa91K0RQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-color-hex-alpha@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.3.tgz#61a0fd151d28b128aa6a8a21a2dad24eebb34d52" + integrity sha512-fESawWJCrBV035DcbKRPAVmy21LpoyiXdPTuHUfWJ14ZRjY7Y7PA6P4g8z6LQGYhU1WAxkTxjIjurXzoe68Glw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-color-rebeccapurple@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.0.2.tgz#5d397039424a58a9ca628762eb0b88a61a66e079" + integrity sha512-SFc3MaocHaQ6k3oZaFwH8io6MdypkUtEy/eXzXEB1vEQlO3S3oDc/FSZA8AsS04Z25RirQhlDlHLh3dn7XewWw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-colormin@^*: + version "5.3.0" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" + integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.0.tgz#f8d3abe40b4ce4b1470702a0706343eac17e7c10" + integrity sha512-GkyPbZEYJiWtQB0KZ0X6qusqFHUepguBCNFi9t5JJc7I2OTXG7C0twbTLvCfaKOLl3rSXmpAwV7W5txd91V84g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-custom-media@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz#1be6aff8be7dc9bf1fe014bde3b71b92bb4552f1" + integrity sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g== + +postcss-custom-properties@^12.1.4: + version "12.1.4" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-12.1.4.tgz#e3d8a8000f28094453b836dff5132385f2862285" + integrity sha512-i6AytuTCoDLJkWN/MtAIGriJz3j7UX6bV7Z5t+KgFz+dwZS15/mlTJY1S0kRizlk6ba0V8u8hN50Fz5Nm7tdZw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-custom-selectors@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-6.0.0.tgz#022839e41fbf71c47ae6e316cb0e6213012df5ef" + integrity sha512-/1iyBhz/W8jUepjGyu7V1OPcGbc636snN1yXEQCinb6Bwt7KxsiU7/bLQlp8GwAXzCh7cobBU5odNn/2zQWR8Q== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-dir-pseudo-class@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.4.tgz#9afe49ea631f0cb36fa0076e7c2feb4e7e3f049c" + integrity sha512-I8epwGy5ftdzNWEYok9VjW9whC4xnelAtbajGv4adql4FIF09rnrxnA9Y8xSHN47y7gqFIv10C5+ImsLeJpKBw== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-discard-comments@^*: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz#e90019e1a0e5b99de05f63516ce640bd0df3d369" + integrity sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ== + +postcss-discard-duplicates@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" + integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== + +postcss-discard-empty@^*: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" + integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== + +postcss-discard-overridden@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" + integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== + +postcss-double-position-gradients@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.1.tgz#a12cfdb7d11fa1a99ccecc747f0c19718fb37152" + integrity sha512-jM+CGkTs4FcG53sMPjrrGE0rIvLDdCrqMzgDC5fLI7JHDO7o6QG8C5TQBtExb13hdBdoH9C2QVbG4jo2y9lErQ== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^1.1.0" + postcss-value-parser "^4.2.0" + +postcss-env-function@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-4.0.5.tgz#b9614d50abd91e4c88a114644a9766880dabe393" + integrity sha512-gPUJc71ji9XKyl0WSzAalBeEA/89kU+XpffpPxSaaaZ1c48OL36r1Ep5R6+9XAPkIiDlSvVAwP4io12q/vTcvA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-flexbugs-fixes@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz#2028e145313074fc9abe276cb7ca14e5401eb49d" + integrity sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ== + +postcss-focus-visible@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz#50c9ea9afa0ee657fb75635fabad25e18d76bf9e" + integrity sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-focus-within@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz#5b1d2ec603195f3344b716c0b75f61e44e8d2e20" + integrity sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-font-variant@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" + integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA== + +postcss-gap-properties@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz#6401bb2f67d9cf255d677042928a70a915e6ba60" + integrity sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ== + +postcss-image-set-function@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-4.0.6.tgz#bcff2794efae778c09441498f40e0c77374870a9" + integrity sha512-KfdC6vg53GC+vPd2+HYzsZ6obmPqOk6HY09kttU19+Gj1nC3S3XBVEXDHxkhxTohgZqzbUb94bKXvKDnYWBm/A== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-initial@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-4.0.1.tgz#529f735f72c5724a0fb30527df6fb7ac54d7de42" + integrity sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ== + +postcss-js@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00" + integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== + dependencies: + camelcase-css "^2.0.1" + +postcss-lab-function@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.1.2.tgz#b75afe43ba9c1f16bfe9bb12c8109cabd55b5fc2" + integrity sha512-isudf5ldhg4fk16M8viAwAbg6Gv14lVO35N3Z/49NhbwPQ2xbiEoHgrRgpgQojosF4vF7jY653ktB6dDrUOR8Q== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^1.1.0" + postcss-value-parser "^4.2.0" + +postcss-load-config@^3.1.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.3.tgz#21935b2c43b9a86e6581a576ca7ee1bde2bd1d23" + integrity sha512-5EYgaM9auHGtO//ljHH+v/aC/TQ5LHXtL7bQajNAUBKUVKiYE8rYpFms7+V26D9FncaGe2zwCoPQsFKb5zF/Hw== + dependencies: + lilconfig "^2.0.4" + yaml "^1.10.2" + +postcss-loader@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" + integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== + dependencies: + cosmiconfig "^7.0.0" + klona "^2.0.5" + semver "^7.3.5" + +postcss-logical@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-5.0.4.tgz#ec75b1ee54421acc04d5921576b7d8db6b0e6f73" + integrity sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g== + +postcss-media-minmax@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5" + integrity sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ== + +postcss-merge-longhand@^*: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.2.tgz#fe3002f38ad5827c1d6f7d5bb3f71d2566a2a138" + integrity sha512-18/bp9DZnY1ai9RlahOfLBbmIUKfKFPASxRCiZ1vlpZqWPCn8qWPFlEozqmWL+kBtcEQmG8W9YqGCstDImvp/Q== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^*" + +postcss-merge-rules@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.0.tgz#a2d5117eba09c8686a5471d97bd9afcf30d1b41f" + integrity sha512-NecukEJovQ0mG7h7xV8wbYAkXGTO3MPKnXvuiXzOKcxoOodfTTKYjeo8TMhAswlSkjcPIBlnKbSFcTuVSDaPyQ== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + cssnano-utils "^3.1.0" + postcss-selector-parser "^6.0.5" + +postcss-minify-font-values@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" + integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.0.tgz#de0260a67a13b7b321a8adc3150725f2c6612377" + integrity sha512-J/TMLklkONn3LuL8wCwfwU8zKC1hpS6VcxFkNUNjmVt53uKqrrykR3ov11mdUYyqVMEx67slMce0tE14cE4DTg== + dependencies: + colord "^2.9.1" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^*: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.1.tgz#c5f8e7dac565e577dd99904787fbec576cbdbfb2" + integrity sha512-WCpr+J9Uz8XzMpAfg3UL8z5rde6MifBbh5L8bn8S2F5hq/YDJJzASYCnCHvAB4Fqb94ys8v95ULQkW2EhCFvNg== + dependencies: + browserslist "^4.16.6" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^*: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.0.tgz#17c2be233e12b28ffa8a421a02fc8b839825536c" + integrity sha512-vYxvHkW+iULstA+ctVNx0VoRAR4THQQRkG77o0oa4/mBS0OzGvvzLIvHDv/nNEM0crzN2WIyFU5X7wZhaUK3RA== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" + integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" + integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-nested@5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" + integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA== + dependencies: + postcss-selector-parser "^6.0.6" + +postcss-nesting@^10.1.2: + version "10.1.3" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.1.3.tgz#f0b1cd7ae675c697ab6a5a5ca1feea4784a2ef77" + integrity sha512-wUC+/YCik4wH3StsbC5fBG1s2Z3ZV74vjGqBFYtmYKlVxoio5TYGM06AiaKkQPPlkXWn72HKfS7Cw5PYxnoXSw== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-normalize-charset@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" + integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== + +postcss-normalize-display-values@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" + integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-positions@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.0.tgz#902a7cb97cf0b9e8b1b654d4a43d451e48966458" + integrity sha512-8gmItgA4H5xiUxgN/3TVvXRoJxkAWLW6f/KKhdsH03atg0cB8ilXnrB5PpSshwVu/dD2ZsRFQcR1OEmSBDAgcQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-repeat-style@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.0.tgz#f6d6fd5a54f51a741cc84a37f7459e60ef7a6398" + integrity sha512-IR3uBjc+7mcWGL6CtniKNQ4Rr5fTxwkaDHwMBDGGs1x9IVRkYIT/M4NelZWkAOBdV6v3Z9S46zqaKGlyzHSchw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-string@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" + integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-timing-functions@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" + integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-unicode@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75" + integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ== + dependencies: + browserslist "^4.16.6" + postcss-value-parser "^4.2.0" + +postcss-normalize-url@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" + integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== + dependencies: + normalize-url "^6.0.1" + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^*: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" + integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize/-/postcss-normalize-10.0.1.tgz#464692676b52792a06b06880a176279216540dd7" + integrity sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA== + dependencies: + "@csstools/normalize.css" "*" + postcss-browser-comments "^4" + sanitize.css "*" + +postcss-opacity-percentage@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz#bd698bb3670a0a27f6d657cc16744b3ebf3b1145" + integrity sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w== + +postcss-ordered-values@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.0.tgz#04ef429e0991b0292bc918b135cd4c038f7b889f" + integrity sha512-wU4Z4D4uOIH+BUKkYid36gGDJNQtkVJT7Twv8qH6UyfttbbJWyw4/xIPuVEkkCtQLAJ0EdsNSh8dlvqkXb49TA== + dependencies: + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-overflow-shorthand@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz#ebcfc0483a15bbf1b27fdd9b3c10125372f4cbc2" + integrity sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg== + +postcss-page-break@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" + integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ== + +postcss-place@^7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-7.0.4.tgz#eb026650b7f769ae57ca4f938c1addd6be2f62c9" + integrity sha512-MrgKeiiu5OC/TETQO45kV3npRjOFxEHthsqGtkh3I1rPbZSbXGD/lZVi9j13cYh+NA8PIAPyk6sGjT9QbRyvSg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-preset-env@^7.0.1: + version "7.4.2" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-7.4.2.tgz#2ff3e4787bd9d89710659535855d6ce85ce6110b" + integrity sha512-AmOkb8AeNNQwE/z2fHl1iwOIt8J50V8WR0rmLagcgIDoqlJZWjV3NdtOPnLGco1oN8DZe+Ss5B9ULbBeS6HfeA== + dependencies: + "@csstools/postcss-color-function" "^1.0.2" + "@csstools/postcss-font-format-keywords" "^1.0.0" + "@csstools/postcss-hwb-function" "^1.0.0" + "@csstools/postcss-ic-unit" "^1.0.0" + "@csstools/postcss-is-pseudo-class" "^2.0.0" + "@csstools/postcss-normalize-display-values" "^1.0.0" + "@csstools/postcss-oklab-function" "^1.0.1" + "@csstools/postcss-progressive-custom-properties" "^1.2.0" + autoprefixer "^10.4.2" + browserslist "^4.19.3" + css-blank-pseudo "^3.0.3" + css-has-pseudo "^3.0.4" + css-prefers-color-scheme "^6.0.3" + cssdb "^6.4.0" + postcss-attribute-case-insensitive "^5.0.0" + postcss-color-functional-notation "^4.2.2" + postcss-color-hex-alpha "^8.0.3" + postcss-color-rebeccapurple "^7.0.2" + postcss-custom-media "^8.0.0" + postcss-custom-properties "^12.1.4" + postcss-custom-selectors "^6.0.0" + postcss-dir-pseudo-class "^6.0.4" + postcss-double-position-gradients "^3.1.0" + postcss-env-function "^4.0.5" + postcss-focus-visible "^6.0.4" + postcss-focus-within "^5.0.4" + postcss-font-variant "^5.0.0" + postcss-gap-properties "^3.0.3" + postcss-image-set-function "^4.0.6" + postcss-initial "^4.0.1" + postcss-lab-function "^4.1.1" + postcss-logical "^5.0.4" + postcss-media-minmax "^5.0.0" + postcss-nesting "^10.1.2" + postcss-opacity-percentage "^1.1.2" + postcss-overflow-shorthand "^3.0.3" + postcss-page-break "^3.0.4" + postcss-place "^7.0.4" + postcss-pseudo-class-any-link "^7.1.1" + postcss-replace-overflow-wrap "^4.0.0" + postcss-selector-not "^5.0.0" + postcss-value-parser "^4.2.0" + +postcss-pseudo-class-any-link@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.1.tgz#534eb1dadd9945eb07830dbcc06fb4d5d865b8e0" + integrity sha512-JRoLFvPEX/1YTPxRxp1JO4WxBVXJYrSY7NHeak5LImwJ+VobFMwYDQHvfTXEpcn+7fYIeGkC29zYFhFWIZD8fg== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-reduce-initial@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6" + integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" + integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-replace-overflow-wrap@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" + integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== + +postcss-selector-not@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-5.0.0.tgz#ac5fc506f7565dd872f82f5314c0f81a05630dc7" + integrity sha512-/2K3A4TCP9orP4TNS7u3tGdRFVKqz/E6pX3aGnriPG0jU78of8wsUcqE4QAhWEU0d+WnMSF93Ah3F//vUtK+iQ== + dependencies: + balanced-match "^1.0.0" + +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" + integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-svgo@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" + integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^2.7.0" + +postcss-unique-selectors@^*: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" + integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-value-parser@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^7.0.35: + version "7.0.39" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" + integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== + dependencies: + picocolors "^0.2.1" + source-map "^0.6.1" + +postcss@^8.1.0, postcss@^8.3.5, postcss@^8.4.4, postcss@^8.4.6, postcss@^8.4.7: + version "8.4.12" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" + integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== + dependencies: + nanoid "^3.3.1" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.2.1, prettier@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.0.tgz#12f8f504c4d8ddb76475f441337542fa799207d4" + integrity sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A== + +pretty-bytes@^5.3.0, pretty-bytes@^5.4.1: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + +pretty-error@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" + integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== + dependencies: + lodash "^4.17.20" + renderkid "^3.0.0" + +pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +promise-polyfill@^8.1.3, promise-polyfill@^8.2.1: + version "8.2.3" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.3.tgz#2edc7e4b81aff781c88a0d577e5fe9da822107c6" + integrity sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg== + +promise@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" + integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q== + dependencies: + asap "~2.0.6" + +prompts@^2.0.1, prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +prop-types@^15.5.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +psl@^1.1.33: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qs@6.9.7: + version "6.9.7" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" + integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" + integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== + dependencies: + bytes "3.1.2" + http-errors "1.8.1" + iconv-lite "0.4.24" + unpipe "1.0.0" + +react-app-polyfill@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz#95221e0a9bd259e5ca6b177c7bb1cb6768f68fd7" + integrity sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w== + dependencies: + core-js "^3.19.2" + object-assign "^4.1.1" + promise "^8.1.0" + raf "^3.4.1" + regenerator-runtime "^0.13.9" + whatwg-fetch "^3.6.2" + +react-dev-utils@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526" + integrity sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ== + dependencies: + "@babel/code-frame" "^7.16.0" + address "^1.1.2" + browserslist "^4.18.1" + chalk "^4.1.2" + cross-spawn "^7.0.3" + detect-port-alt "^1.1.6" + escape-string-regexp "^4.0.0" + filesize "^8.0.6" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.5.0" + global-modules "^2.0.0" + globby "^11.0.4" + gzip-size "^6.0.0" + immer "^9.0.7" + is-root "^2.1.0" + loader-utils "^3.2.0" + open "^8.4.0" + pkg-up "^3.1.0" + prompts "^2.4.2" + react-error-overlay "^6.0.10" + recursive-readdir "^2.2.2" + shell-quote "^1.7.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.2" + +react-error-overlay@^6.0.10: + version "6.0.10" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" + integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== + +react-is@^16.10.2, react-is@^16.13.1, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.1, react-is@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-modal@^3.12.1, react-modal@^3.14.4: + version "3.14.4" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.14.4.tgz#2ca7e8e9a180955e5c9508c228b73167c1e6f6a3" + integrity sha512-8surmulejafYCH9wfUmFyj4UfbSJwjcgbS9gf3oOItu4Hwd6ivJyVBETI0yHRhpJKCLZMUtnhzk76wXTsNL6Qg== + dependencies: + exenv "^1.2.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.0" + warning "^4.0.3" + +react-refresh@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" + integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== + +react-resize-detector@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-7.1.2.tgz#8ef975dd8c3d56f9a5160ac382ef7136dcd2d86c" + integrity sha512-zXnPJ2m8+6oq9Nn8zsep/orts9vQv3elrpA+R8XTcW7DVVUJ9vwDwMXaBtykAYjMnkCIaOoK9vObyR7ZgFNlOw== + dependencies: + lodash "^4.17.21" + +react-router-dom@^6: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d" + integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw== + dependencies: + history "^5.2.0" + react-router "6.3.0" + +react-router@6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" + integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== + dependencies: + history "^5.2.0" + +react-scripts@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.0.tgz#6547a6d7f8b64364ef95273767466cc577cb4b60" + integrity sha512-3i0L2CyIlROz7mxETEdfif6Sfhh9Lfpzi10CtcGs1emDQStmZfWjJbAIMtRD0opVUjQuFWqHZyRZ9PPzKCFxWg== + dependencies: + "@babel/core" "^7.16.0" + "@pmmmwh/react-refresh-webpack-plugin" "^0.5.3" + "@svgr/webpack" "^5.5.0" + babel-jest "^27.4.2" + babel-loader "^8.2.3" + babel-plugin-named-asset-import "^0.3.8" + babel-preset-react-app "^10.0.1" + bfj "^7.0.2" + browserslist "^4.18.1" + camelcase "^6.2.1" + case-sensitive-paths-webpack-plugin "^2.4.0" + css-loader "^6.5.1" + css-minimizer-webpack-plugin "^3.2.0" + dotenv "^10.0.0" + dotenv-expand "^5.1.0" + eslint "^8.3.0" + eslint-config-react-app "^7.0.0" + eslint-webpack-plugin "^3.1.1" + file-loader "^6.2.0" + fs-extra "^10.0.0" + html-webpack-plugin "^5.5.0" + identity-obj-proxy "^3.0.0" + jest "^27.4.3" + jest-resolve "^27.4.2" + jest-watch-typeahead "^1.0.0" + mini-css-extract-plugin "^2.4.5" + postcss "^8.4.4" + postcss-flexbugs-fixes "^5.0.2" + postcss-loader "^6.2.1" + postcss-normalize "^10.0.1" + postcss-preset-env "^7.0.1" + prompts "^2.4.2" + react-app-polyfill "^3.0.0" + react-dev-utils "^12.0.0" + react-refresh "^0.11.0" + resolve "^1.20.0" + resolve-url-loader "^4.0.0" + sass-loader "^12.3.0" + semver "^7.3.5" + source-map-loader "^3.0.0" + style-loader "^3.3.1" + tailwindcss "^3.0.2" + terser-webpack-plugin "^5.2.5" + webpack "^5.64.4" + webpack-dev-server "^4.6.0" + webpack-manifest-plugin "^4.0.2" + workbox-webpack-plugin "^6.4.1" + optionalDependencies: + fsevents "^2.3.2" + +react-smooth@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-2.0.1.tgz#74c7309916d6ccca182c4b30c8992f179e6c5a05" + integrity sha512-Own9TA0GPPf3as4vSwFhDouVfXP15ie/wIHklhyKBH5AN6NFtdk0UpHBnonV11BtqDkAWlt40MOUc+5srmW7NA== + dependencies: + fast-equals "^2.0.0" + react-transition-group "2.9.0" + +react-spring@^9.4.3: + version "9.4.4" + resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-9.4.4.tgz#0a53440013f97c1ebaa739a24a902487f36a5c1e" + integrity sha512-VOqilh9DJBsS6Pf550YLhdReS3j9a2AQVh7NcsNtWoxTYIeuErWi6ym0++6bBhQp4yT5xvVvUDaJ8ez8vrFgaw== + dependencies: + "@react-spring/core" "~9.4.4" + "@react-spring/konva" "~9.4.4" + "@react-spring/native" "~9.4.4" + "@react-spring/three" "~9.4.4" + "@react-spring/web" "~9.4.4" + "@react-spring/zdog" "~9.4.4" + +react-tabs@^3.2.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-3.2.3.tgz#ccbb3e1241ad3f601047305c75db661239977f2f" + integrity sha512-jx325RhRVnS9DdFbeF511z0T0WEqEoMl1uCE3LoZ6VaZZm7ytatxbum0B8bCTmaiV0KsU+4TtLGTGevCic7SWg== + dependencies: + clsx "^1.1.0" + prop-types "^15.5.0" + +react-toast-notifications@^2.4.3: + version "2.5.1" + resolved "https://registry.yarnpkg.com/react-toast-notifications/-/react-toast-notifications-2.5.1.tgz#30216eedb5608ec69719a818b9a2e09283e90074" + integrity sha512-eYuuiSPGLyuMHojRH2U7CbENvFHsvNia39pLM/s10KipIoNs14T7RIJk4aU2N+l++OsSgtJqnFObx9bpwLMU5A== + dependencies: + "@emotion/core" "^10.0.14" + react-transition-group "^4.4.1" + +react-transition-group@2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" + integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== + dependencies: + dom-helpers "^3.4.0" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-lifecycles-compat "^3.0.4" + +react-transition-group@^4.4.1: + version "4.4.2" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470" + integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +readable-stream@^2.0.1: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +recharts-scale@^0.4.4: + version "0.4.5" + resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9" + integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w== + dependencies: + decimal.js-light "^2.4.1" + +recharts@^2.1.13: + version "2.1.13" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.1.13.tgz#61801acf3e13896b07dc6a8b38cbdd648480d0b7" + integrity sha512-9VWu2nzExmfiMFDHKqRFhYlJVmjzQGVKH5rBetXR4EuyEXuu3Y6cVxQuNEdusHhbm4SoPPrVDCwlBdREL3sQPA== + dependencies: + classnames "^2.2.5" + d3-interpolate "^3.0.1" + d3-scale "^4.0.2" + d3-shape "^3.1.0" + eventemitter3 "^4.0.1" + lodash "^4.17.19" + react-is "^16.10.2" + react-resize-detector "^7.1.2" + react-smooth "^2.0.1" + recharts-scale "^0.4.4" + reduce-css-calc "^2.1.8" + +recursive-readdir@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" + integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== + dependencies: + minimatch "3.0.4" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +reduce-css-calc@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03" + integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg== + dependencies: + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" + +regenerate-unicode-properties@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" + integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.9: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regenerator-transform@^0.14.2: + version "0.14.5" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" + integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== + dependencies: + "@babel/runtime" "^7.8.4" + +regex-parser@^2.2.11: + version "2.2.11" + resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58" + integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== + +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" + integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +regexpu-core@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.0.1.tgz#c531122a7840de743dcf9c83e923b5560323ced3" + integrity sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.0.0" + +regjsgen@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" + integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== + +regjsparser@^0.8.2: + version "0.8.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" + integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== + dependencies: + jsesc "~0.5.0" + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= + +renderkid@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" + integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== + dependencies: + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^6.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-url-loader@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz#d50d4ddc746bb10468443167acf800dcd6c3ad57" + integrity sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA== + dependencies: + adjust-sourcemap-loader "^4.0.0" + convert-source-map "^1.7.0" + loader-utils "^2.0.0" + postcss "^7.0.35" + source-map "0.6.1" + +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + dependencies: + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.3: + version "2.0.0-next.3" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" + integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup-plugin-terser@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" + integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ== + dependencies: + "@babel/code-frame" "^7.10.4" + jest-worker "^26.2.1" + serialize-javascript "^4.0.0" + terser "^5.0.0" + +rollup@^2.43.1: + version "2.70.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.1.tgz#824b1f1f879ea396db30b0fc3ae8d2fead93523e" + integrity sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA== + optionalDependencies: + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sanitize.css@*: + version "13.0.0" + resolved "https://registry.yarnpkg.com/sanitize.css/-/sanitize.css-13.0.0.tgz#2675553974b27964c75562ade3bd85d79879f173" + integrity sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA== + +sass-loader@^12.3.0: + version "12.6.0" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb" + integrity sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA== + dependencies: + klona "^2.0.4" + neo-async "^2.6.2" + +sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +schema-utils@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== + dependencies: + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" + ajv-keywords "^3.4.1" + +schema-utils@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" + integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.8.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.0.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= + +selfsigned@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.0.tgz#e927cd5377cbb0a1075302cff8df1042cc2bce5b" + integrity sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ== + dependencies: + node-forge "^1.2.0" + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.2, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +send@0.17.2: + version "0.17.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" + integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "1.8.1" + mime "1.6.0" + ms "2.1.3" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.14.2: + version "1.14.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" + integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.2" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" + integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + +sockjs@^0.3.21: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +source-list-map@^2.0.0, source-list-map@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-js@^1.0.1, source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map-loader@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-3.0.1.tgz#9ae5edc7c2d42570934be4c95d1ccc6352eba52d" + integrity sha512-Vp1UsfyPvgujKQzi4pyDiTOnE3E4H+yHvkVRN3c/9PJmQS4CQJExvcDvaX/D+RV+xQben9HJ56jMJS3CgUeWyA== + dependencies: + abab "^2.0.5" + iconv-lite "^0.6.3" + source-map-js "^1.0.1" + +source-map-resolve@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" + integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + +source-map-support@^0.5.6, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.5.0, source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +source-map@^0.8.0-beta.0: + version "0.8.0-beta.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" + integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== + dependencies: + whatwg-url "^7.0.0" + +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" + +stackframe@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.1.tgz#1033a3473ee67f08e2f2fc8eba6aef4f845124e1" + integrity sha512-h88QkzREN/hy8eRdyNhhsO7RSJ5oyTqxxmmn0dzBIMUclZsjpfmrsg81vp8mjjAs2vAZ72nyWxRUwSwmh0e4xg== + +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-length@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-5.0.1.tgz#3d647f497b6e8e8d41e422f7e0b23bc536c8381e" + integrity sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow== + dependencies: + char-regex "^2.0.0" + strip-ansi "^7.0.1" + +string-natural-compare@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" + integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string.prototype.matchall@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz#5abb5dabc94c7b0ea2380f65ba610b3a544b15fa" + integrity sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + regexp.prototype.flags "^1.3.1" + side-channel "^1.0.4" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.0, strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-comments@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-2.0.1.tgz#4ad11c3fbcac177a67a40ac224ca339ca1c1ba9b" + integrity sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +style-loader@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" + integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== + +styled-components@^5.2.1, styled-components@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.3.tgz#312a3d9a549f4708f0fb0edc829eb34bde032743" + integrity sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/traverse" "^7.4.5" + "@emotion/is-prop-valid" "^0.8.8" + "@emotion/stylis" "^0.8.4" + "@emotion/unitless" "^0.7.4" + babel-plugin-styled-components ">= 1.12.0" + css-to-react-native "^3.0.0" + hoist-non-react-statics "^3.0.0" + shallowequal "^1.1.0" + supports-color "^5.5.0" + +stylehacks@^*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520" + integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q== + dependencies: + browserslist "^4.16.6" + postcss-selector-parser "^6.0.4" + +supports-color@^5.3.0, supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svg-parser@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + +svgo@^1.2.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + +svgo@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +tailwindcss@^3.0.2: + version "3.0.23" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.23.tgz#c620521d53a289650872a66adfcb4129d2200d10" + integrity sha512-+OZOV9ubyQ6oI2BXEhzw4HrqvgcARY38xv3zKcjnWtMIZstEsXdI9xftd1iB7+RbOnj2HOEzkA0OyB5BaSxPQA== + dependencies: + arg "^5.0.1" + chalk "^4.1.2" + chokidar "^3.5.3" + color-name "^1.1.4" + cosmiconfig "^7.0.1" + detective "^5.2.0" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.11" + glob-parent "^6.0.2" + is-glob "^4.0.3" + normalize-path "^3.0.0" + object-hash "^2.2.0" + postcss "^8.4.6" + postcss-js "^4.0.0" + postcss-load-config "^3.1.0" + postcss-nested "5.0.6" + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" + quick-lru "^5.1.1" + resolve "^1.22.0" + +tapable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + +tempy@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.6.0.tgz#65e2c35abc06f1124a97f387b08303442bde59f3" + integrity sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw== + dependencies: + is-stream "^2.0.0" + temp-dir "^2.0.0" + type-fest "^0.16.0" + unique-string "^2.0.0" + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.5: + version "5.3.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz#0320dcc270ad5372c1e8993fabbd927929773e54" + integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g== + dependencies: + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + terser "^5.7.2" + +terser@^5.0.0, terser@^5.10.0, terser@^5.7.2: + version "5.14.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" + integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== + dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" + commander "^2.20.0" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + dependencies: + punycode "^2.1.0" + +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + +tryer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" + integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== + +tsconfig-paths@^3.12.0, tsconfig-paths@^3.9.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.0.tgz#4fcc48f9ccea8826c41b9ca093479de7f5018976" + integrity sha512-cg/1jAZoL57R39+wiw4u/SCC6Ic9Q5NqjBOb+9xISedOYurfog9ZNmKJSxAnb2m/5Bq4lE9lhUcau33Ml8DM0g== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.3: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" + integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^4.4.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" + integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== + +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" + integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" + integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + +universalify@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +upath@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +v8-to-istanbul@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" + integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + +walker@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +watchpack@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" + integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +web-vitals@^2.1.0: + version "2.1.4" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c" + integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +webpack-dev-middleware@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz#aa079a8dedd7e58bfeab358a9af7dab304cee57f" + integrity sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg== + dependencies: + colorette "^2.0.10" + memfs "^3.4.1" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@^4.6.0: + version "4.7.4" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz#d0ef7da78224578384e795ac228d8efb63d5f945" + integrity sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.2.2" + ansi-html-community "^0.0.8" + bonjour "^3.5.0" + chokidar "^3.5.3" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^1.6.0" + default-gateway "^6.0.3" + del "^6.0.0" + express "^4.17.1" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.0" + ipaddr.js "^2.0.1" + open "^8.0.9" + p-retry "^4.5.0" + portfinder "^1.0.28" + schema-utils "^4.0.0" + selfsigned "^2.0.0" + serve-index "^1.9.1" + sockjs "^0.3.21" + spdy "^4.0.2" + strip-ansi "^7.0.0" + webpack-dev-middleware "^5.3.1" + ws "^8.4.2" + +webpack-manifest-plugin@^4.0.2: + version "4.1.1" + resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz#10f8dbf4714ff93a215d5a45bcc416d80506f94f" + integrity sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow== + dependencies: + tapable "^2.0.0" + webpack-sources "^2.2.0" + +webpack-sources@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack-sources@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.3.1.tgz#570de0af163949fe272233c2cefe1b56f74511fd" + integrity sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA== + dependencies: + source-list-map "^2.0.1" + source-map "^0.6.1" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.64.4: + version "5.70.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.70.0.tgz#3461e6287a72b5e6e2f4872700bc8de0d7500e6d" + integrity sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.4.1" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.9.2" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-better-errors "^1.0.2" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.1.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.3.1" + webpack-sources "^3.2.3" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-fetch@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" + integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== + +whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3, word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +workbox-background-sync@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.1.tgz#df79c6a4a22945d8a44493a4947a6ed0f720ef86" + integrity sha512-T5a35fagLXQvV8Dr4+bDU+XYsP90jJ3eBLjZMKuCNELMQZNj+VekCODz1QK44jgoBeQk+vp94pkZV6G+e41pgg== + dependencies: + idb "^6.1.4" + workbox-core "6.5.1" + +workbox-broadcast-update@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.5.1.tgz#9aecb116979b0709480b84cfd1beca7a901d01d4" + integrity sha512-mb/oyblyEpDbw167cCTyHnC3RqCnCQHtFYuYZd+QTpuExxM60qZuBH1AuQCgvLtDcztBKdEYK2VFD9SZYgRbaQ== + dependencies: + workbox-core "6.5.1" + +workbox-build@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.5.1.tgz#6b5e8f090bb608267868540d3072b44b8531b3bc" + integrity sha512-coDUDzHvFZ1ADOl3wKCsCSyOBvkPKlPgcQDb6LMMShN1zgF31Mev/1HzN3+9T2cjjWAgFwZKkuRyExqc1v21Zw== + dependencies: + "@apideck/better-ajv-errors" "^0.3.1" + "@babel/core" "^7.11.1" + "@babel/preset-env" "^7.11.0" + "@babel/runtime" "^7.11.2" + "@rollup/plugin-babel" "^5.2.0" + "@rollup/plugin-node-resolve" "^11.2.1" + "@rollup/plugin-replace" "^2.4.1" + "@surma/rollup-plugin-off-main-thread" "^2.2.3" + ajv "^8.6.0" + common-tags "^1.8.0" + fast-json-stable-stringify "^2.1.0" + fs-extra "^9.0.1" + glob "^7.1.6" + lodash "^4.17.20" + pretty-bytes "^5.3.0" + rollup "^2.43.1" + rollup-plugin-terser "^7.0.0" + source-map "^0.8.0-beta.0" + stringify-object "^3.3.0" + strip-comments "^2.0.1" + tempy "^0.6.0" + upath "^1.2.0" + workbox-background-sync "6.5.1" + workbox-broadcast-update "6.5.1" + workbox-cacheable-response "6.5.1" + workbox-core "6.5.1" + workbox-expiration "6.5.1" + workbox-google-analytics "6.5.1" + workbox-navigation-preload "6.5.1" + workbox-precaching "6.5.1" + workbox-range-requests "6.5.1" + workbox-recipes "6.5.1" + workbox-routing "6.5.1" + workbox-strategies "6.5.1" + workbox-streams "6.5.1" + workbox-sw "6.5.1" + workbox-window "6.5.1" + +workbox-cacheable-response@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.5.1.tgz#f71d0a75b3d6846e39594955e99ac42fd26f8693" + integrity sha512-3TdtH/luDiytmM+Cn72HCBLZXmbeRNJqZx2yaVOfUZhj0IVwZqQXhNarlGE9/k6U5Jelb+TtpH2mLVhnzfiSMg== + dependencies: + workbox-core "6.5.1" + +workbox-core@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.5.1.tgz#0dba3bccf883a46dfa61cc412eaa3cb09bb549e6" + integrity sha512-qObXZ39aFJ2N8X7IUbGrJHKWguliCuU1jOXM/I4MTT84u9BiKD2rHMkIzgeRP1Ixu9+cXU4/XHJq3Cy0Qqc5hw== + +workbox-expiration@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.5.1.tgz#9f105fcf3362852754884ad153888070ce98b692" + integrity sha512-iY/cTADAQATMmPkUBRmQdacqq0TJd2wMHimBQz+tRnPGHSMH+/BoLPABPnu7O7rT/g/s59CUYYRGxe3mEgoJCA== + dependencies: + idb "^6.1.4" + workbox-core "6.5.1" + +workbox-google-analytics@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.5.1.tgz#685224d439c1e7a943f8241d65e2a34ee95a4ba0" + integrity sha512-qZU46/h4dbionYT6Yk6iBkUwpiEzAfnO1W7KkI+AMmY7G9/gA03dQQ7rpTw8F4vWrG7ahTUGWDFv6fERtaw1BQ== + dependencies: + workbox-background-sync "6.5.1" + workbox-core "6.5.1" + workbox-routing "6.5.1" + workbox-strategies "6.5.1" + +workbox-navigation-preload@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.5.1.tgz#a244e3bdf99ce86da7210315ca1ba5aef3710825" + integrity sha512-aKrgAbn2IMgzTowTi/ZyKdQUcES2m++9aGtpxqsX7Gn9ovCY8zcssaMEAMMwrIeveij5HiWNBrmj6MWDHi+0rg== + dependencies: + workbox-core "6.5.1" + +workbox-precaching@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.5.1.tgz#177b6424f1e71e601b9c3d6864decad2655f9ff9" + integrity sha512-EzlPBxvmjGfE56YZzsT/vpVkpLG1XJhoplgXa5RPyVWLUL1LbwEAxhkrENElSS/R9tgiTw80IFwysidfUqLihg== + dependencies: + workbox-core "6.5.1" + workbox-routing "6.5.1" + workbox-strategies "6.5.1" + +workbox-range-requests@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.5.1.tgz#f40f84aa8765940543eba16131d02f12b38e2fdc" + integrity sha512-57Da/qRbd9v33YlHX0rlSUVFmE4THCjKqwkmfhY3tNLnSKN2L5YBS3qhWeDO0IrMNgUj+rGve2moKYXeUqQt4A== + dependencies: + workbox-core "6.5.1" + +workbox-recipes@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.5.1.tgz#d2fb21743677cc3ca9e1fc9e3b68f0d1587df205" + integrity sha512-DGsyKygHggcGPQpWafC/Nmbm1Ny3sB2vE9r//3UbeidXiQ+pLF14KEG1/0NNGRaY+lfOXOagq6d1H7SC8KA+rA== + dependencies: + workbox-cacheable-response "6.5.1" + workbox-core "6.5.1" + workbox-expiration "6.5.1" + workbox-precaching "6.5.1" + workbox-routing "6.5.1" + workbox-strategies "6.5.1" + +workbox-routing@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.5.1.tgz#5488795ae850fe3ae435241143b54ff25ab0db70" + integrity sha512-yAAncdTwanvlR8KPjubyvFKeAok8ZcIws6UKxvIAg0I+wsf7UYi93DXNuZr6RBSQrByrN6HkCyjuhmk8P63+PA== + dependencies: + workbox-core "6.5.1" + +workbox-strategies@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.5.1.tgz#51cabbddad5a1956eb9d51cf6ce01ab0a6372756" + integrity sha512-JNaTXPy8wXzKkr+6za7/eJX9opoZk7UgY261I2kPxl80XQD8lMjz0vo9EOcBwvD72v3ZhGJbW84ZaDwFEhFvWA== + dependencies: + workbox-core "6.5.1" + +workbox-streams@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.5.1.tgz#12036817385fa4449a86a3ef77fce1cb00ecad9f" + integrity sha512-7jaTWm6HRGJ/ewECnhb+UgjTT50R42E0/uNCC4eTKQwnLO/NzNGjoXTdQgFjo4zteR+L/K6AtFAiYKH3ZJbAYw== + dependencies: + workbox-core "6.5.1" + workbox-routing "6.5.1" + +workbox-sw@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.5.1.tgz#f9256b40f0a7e94656ccd06f127ba19a92cd23c5" + integrity sha512-hVrQa19yo9wzN1fQQ/h2JlkzFpkuH2qzYT2/rk7CLaWt6tLnTJVFCNHlGRRPhytZSf++LoIy7zThT714sowT/Q== + +workbox-webpack-plugin@^6.4.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.1.tgz#da88b4b6d8eff855958f0e7ebb7aa3eea50a8282" + integrity sha512-SHtlQBpKruI16CAYhICDMkgjXE2fH5Yp+D+1UmBfRVhByZYzusVOykvnPm8ObJb9d/tXgn9yoppoxafFS7D4vQ== + dependencies: + fast-json-stable-stringify "^2.1.0" + pretty-bytes "^5.4.1" + upath "^1.2.0" + webpack-sources "^1.4.3" + workbox-build "6.5.1" + +workbox-window@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.5.1.tgz#7b5ca29467b1da45dc9e2b5a1b89159d3eb9957a" + integrity sha512-oRlun9u7b7YEjo2fIDBqJkU2hXtrEljXcOytRhfeQRbqXxjUOpFgXSGRSAkmDx1MlKUNOSbr+zfi8h5n7In3yA== + dependencies: + "@types/trusted-types" "^2.0.2" + workbox-core "6.5.1" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@^7.4.6: + version "7.5.7" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" + integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== + +ws@^8.4.2: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xtend@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==